Skip to main content
Zespan’s PII redaction removes sensitive values from the data captured during LLM calls — message content, tool call arguments, system prompts, and stored prompt/completion text. When a field’s key matches a configured list, its value is replaced with "[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 when storePrompts (store_prompts) is true (the default)
  • tool_call_args — always, regardless of storePrompts
Every provider wrapper (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 via redactKeys (TypeScript) / redact_keys (Python) at initialization.

Default redacted keys

If you don’t set redactKeys / redact_keys, both SDKs fall back to the identical built-in list:
Setting redactKeys / redact_keys replaces the default list — it does not merge with it. If you only pass ["email", "phone"], password and the other built-in keys are no longer redacted. To keep the built-in protection, list the defaults alongside your own keys, as shown above.

How key matching works

Both SDKs implement the same normalization and traversal logic — redact.ts and redact.py are functionally identical:
Matching is case-insensitive and strips every non-alphanumeric character before comparing, so it catches common naming variants against a single configured key:
  • "password" matches password, Password, PASSWORD
  • "api_key" matches api_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 inside tool_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.
It returns a JSON string with matching keys replaced by "[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 a redactPii / 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 list
  • piiCustomPatterns — an array of additional regular expressions to treat as PII

Python: regex pattern detection

Enabling redact_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.
Supported categories: 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 redactKeys if 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. Enabling redactPii / redact_pii addresses this in both SDKs (TypeScript with openredaction, 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: false disables capture, not redaction. Setting storePrompts (store_prompts) to false stops prompt_text/completion_text/system_prompt from being captured at all — it isn’t a redaction mechanism, and it doesn’t affect tool_call_args, which is always redacted and always sent when present.

Redaction and storePrompts

tool_call_args is redacted with redactKeys/redact_keys regardless of the storePrompts setting — it’s a separate field from prompt/completion text.