"[REDACTED]" — the key is preserved so you can still see which field contained sensitive data, but the value never reaches Zespan servers.
Key-based redaction (
redactKeys / redact_keys) matches field names, not free-form text. It’s applied to the structured data captured from LLM calls — message objects, tool call arguments, and similar JSON-shaped fields. It runs identically, locally, and always-on in both SDKs. Pattern-based PII detection (scanning prose for emails, phone numbers, etc.) is a separate feature with very different behavior per language — see Pattern-based PII detection below before relying on it.When redaction runs
Key-based redaction runs inside the SDK, in your process, before an event is added to the send queue — it never depends on a network round trip. Both wrappers apply it to the same fields:prompt_text/completion_text/system_prompt— only whenstorePrompts(store_prompts) istrue(the default)tool_call_args— always, regardless ofstorePrompts
wrapOpenAI/patch_openai, wrapAnthropic/patch_anthropic, and so on) calls this before the event is queued for the batch flush to /v1/ingest — so a matching key’s value is already "[REDACTED]" by the time it leaves your application.
Configuring redaction on init
Key-based redaction is always on — there’s no flag to disable it — but you control the key list viaredactKeys (TypeScript) / redact_keys (Python) at initialization.
Default redacted keys
If you don’t setredactKeys / redact_keys, both SDKs fall back to the identical built-in list:
How key matching works
Both SDKs implement the same normalization and traversal logic —redact.ts and redact.py are functionally identical:
"password"matchespassword,Password,PASSWORD"api_key"matchesapi_key,apiKey,API_KEY,api-key
Nested objects, depth limit, and cyclic safety
Matching walks nested objects and arrays recursively, not just the top level, so a matching key several levels deep insidetool_call_args or a serialized message array is still redacted.
Two safety limits are built in, and are identical between the two SDKs:
Manual redaction (Python)
For cases where you want to redact a value yourself — before logging it or passing it somewhere outside the SDK’s automatic capture path — Python exposes the same key-based redaction as a standalone function from the package root.required
The value to redact. Dicts and lists are redacted recursively; a JSON-encoded string is parsed, redacted, and re-serialized; any other string is returned unchanged.
None returns "".List[str]
required
Field names to redact, matched with the same case-insensitive, punctuation-stripped normalization described above. This is not automatically the client’s configured
redact_keys — pass it explicitly."[REDACTED]":
This helper is public in both SDKs:
redact_for_storage is re-exported from the Python package root (zespan/__init__.py), and redactForStorage is exported from @zespan/sdk. You can call it standalone to redact a value yourself, or rely on the automatic capture pipeline that applies it for you.Pattern-based PII detection
Key-based redaction only catches values stored under a matching field name. It won’t catch an email address or phone number typed into a user’s message, since that’s free-form content, not a keyed field. Both SDKs expose aredactPii / redact_pii option for this, and both run locally, in your process, before the event is transmitted, applying key-based redaction first and PII detection second.
The two SDKs detect PII with different technology. TypeScript uses the
openredaction package — ML-based, confidence-scored, with broad compliance presets. Python uses a fixed set of standard-library regular expressions for a focused category list (email, phone, credit card, SSN, IPv4); there is no confidence score, so pii_confidence_threshold has no effect in Python. Read the per-language sections below for exactly what each catches.TypeScript: local pattern detection
redactPii is off by default. When enabled, the TypeScript SDK lazily loads the openredaction package and scans the same captured content as key-based redaction — message content, tool call arguments, system prompts, and stored prompt/completion text — for common PII patterns, applying key-based redaction first and PII pattern detection second. Detection failures fail open (the original text passes through) rather than crashing your app.
Presets and categories
piiPreset selects a bundled set of categories tuned for a compliance context: gdpr, hipaa, ccpa, pci-dss, soc2, finance, education, transportation.
You can instead set piiCategories directly to choose from: personal, financial, government_ids, healthcare, digital_identity. If both are set, piiCategories takes precedence over piiPreset.
Redaction mode
piiRedactionMode controls how a matched value is replaced:
Confidence and exclusions
piiConfidenceThreshold sets the minimum confidence, from 0 to 1, required before a match is redacted — it defaults to 0.7. piiWhitelist lists values that should never be redacted even if they match a pattern.
Additional TypeScript-only controls
piiIncludeNames,piiIncludeEmails,piiIncludePhones,piiIncludeAddresses— booleans to toggle individual detectors on, independent of a preset or category listpiiCustomPatterns— an array of additional regular expressions to treat as PII
Python: regex pattern detection
Enablingredact_pii=True runs a set of standard-library regex detectors over the same captured content as key-based redaction — locally, in your process, before the event is sent. Key-based redaction runs first, then PII patterns.
email, phone, credit_card (Luhn-checked to cut false positives), ssn (US ###-##-####), ip (IPv4). Leave pii_categories empty to redact all of them.
Redaction mode (pii_redaction_mode):
Python’s detection is regex-based, not the ML detector TypeScript uses — it covers a narrower, well-defined set and can occasionally false-positive (the
phone pattern is intentionally loose). The pii_preset and pii_confidence_threshold fields exist for cross-SDK API compatibility but do not affect Python matching — use pii_categories and pii_redaction_mode to control it.What is NOT redacted — your responsibility
- Content typed into free-form fields is only caught by
redactKeysif it sits under a matching key. An email address embedded inside a user’s chat message body is not redacted by key-based redaction — that’s prose, not a keyed field. EnablingredactPii/redact_piiaddresses this in both SDKs (TypeScript withopenredaction, Python with regex — see above). - Data outside the SDK’s capture path. Redaction only applies to the fields the SDK captures and sends (messages, tool call arguments, system prompts, prompt/completion text). Anything you log, store, or transmit yourself outside of
zespan’s wrappers is untouched by any of this. - Keys not in your configured list. If a sensitive field’s name isn’t in
redactKeys/redact_keys(and doesn’t match a default), its value passes through unredacted. Review the fields your application actually sends to LLMs and make sure your key list covers them. storePrompts: falsedisables capture, not redaction. SettingstorePrompts(store_prompts) tofalsestopsprompt_text/completion_text/system_promptfrom being captured at all — it isn’t a redaction mechanism, and it doesn’t affecttool_call_args, which is always redacted and always sent when present.

