Content capture and privacy
Opt-in, encrypted prompt and completion capture, and how to group multi-turn conversations.
By default the SDK sends only cost metadata: tokens or billing units, model, latency, and tags. Prompt and completion text are not captured.
Hosted OTLP is stricter in this release: it always discards prompt/completion, system instruction, tool argument/result, and binary content before request rows are published. The SDK content-capture setting does not enable hosted OTLP content capture. If OpenRouter Privacy Mode is off, content still traverses the receiver before being discarded; enable it when content must never be transmitted.
Opt-in capture
Organizations on Pro or Enterprise can enable sampled content capture from Settings. Once enabled:
- A sampled subset of prompts and completions is captured automatically.
- Samples are text-only (images, audio, and binary parts are stripped).
- Secrets and common personal identifiers are redacted in your process before a sample is sent (see What is redacted), and content is encrypted at rest.
- Org owners can delete captured content from Settings.
There is nothing to call in application code to turn capture on. The Settings opt-in is always required.
What is redacted
Before a sample leaves your process, the SDK replaces the items below with a typed placeholder. The Python and JavaScript SDKs apply the same rules to the prompt, the outcome, and every string inside tool calls and tool results.
| Placeholder | What it replaces |
|---|---|
[REDACTED_SECRET] | API keys with a known prefix: OpenAI (sk-, sk-proj-, sk-svcacct-, sk-admin-), Anthropic (sk-ant-), OpenRouter (sk-or-v1-), SuperPenguin (sp-, sp_, sp-org.), Stripe secret and restricted keys and webhook secrets, AWS access key IDs, GitHub, GitLab, Slack tokens and webhook URLs, Google API keys and OAuth access tokens, Hugging Face, Groq, xAI, Perplexity, Replicate, npm, PyPI and SendGrid. Cloud credentials: Azure Storage and Cosmos DB account keys (AccountKey=), Azure Service Bus and Event Hubs shared access keys, Azure AD client secrets (…8Q~…), Google OAuth refresh tokens (1//0…), and signatures in signed links (Azure SAS sig=, Google Cloud Storage X-Goog-Signature=, AWS X-Amz-Signature=). Also code= values in links (Azure Functions keys, OAuth authorization codes), JSON Web Tokens, PEM private keys, the token after Bearer or in an Authorization header, the password in a URL (postgres://user:password@host), the value in assignments such as password=..., api_key: ..., secret=... and token=..., and any tool-argument value whose key name ends in password, secret, token, API key, private key, access key, account key, authorization, credential(s) or cookie(s). |
[REDACTED_EMAIL] | Email addresses. |
[REDACTED_PHONE] | International numbers written with a leading + (8 to 15 digits), and North American numbers such as (415) 555-0132, 415-555-0132, 415.555.0132 and 1-800-555-0199. |
[REDACTED_CARD] | Payment card numbers (13 to 19 digits, with or without spaces or dashes) that start with a known card prefix and pass the Luhn check. |
[REDACTED_SSN] | US Social Security numbers written as 123-45-6789 or 123 45 6789. |
[REDACTED_IBAN] | IBANs with a valid country length and checksum. |
[REDACTED_IP] | Public IPv4 and IPv6 addresses. Private, loopback, link-local and documentation ranges are kept. |
Names, quotes and auth schemes around a secret stay readable, for example Authorization: Bearer [REDACTED_SECRET] or DB_PASSWORD=[REDACTED_SECRET].
SuperPenguin applies the [REDACTED_SECRET] rules again when a sample arrives, before it is encrypted, so samples from older SDK versions get the same secret redaction. Personal data is redacted only in your process.
These are not redacted:
- Names, street addresses, dates of birth and other free-text personal data. Detecting them reliably needs a language model or entity recognizer, which the SDK does not bundle.
- National ID numbers other than US SSNs, nine-digit numbers without dashes or spaces, and phone numbers without a
+outside the North American formats above. - Secrets without a recognizable prefix or assignment, for example a random token pasted on its own, an Azure OpenAI key (32 hex characters) without a name such as
api-key:in front of it, or the secret half of a Google Cloud Storage HMAC key. - Metadata values such as
customer_id,session_id,featureandprompt_key. They are sent as you set them, so do not put email addresses or other personal data in them.
A version string shaped like a public IPv4 address (release 1.2.3.4) is masked as an IP address.
Add your own rules
Pass a redactor function to add rules of your own. It runs after the built-in rules, on the prompt text, on the outcome text, and on the tool calls and tool results serialized as one JSON string. A sample it changes is marked as redacted.
import re
import superpenguin as sp
sp.configure_content_capture(
redactor=lambda text: re.sub(r"\bACME-\d{6}\b", "[ACCOUNT]", text),
)import { configureContentCapture } from "@superpenguin/js";
configureContentCapture({
redactor: (text) => text.replace(/\bACME-\d{6}\b/g, "[ACCOUNT]"),
});Each field is stored up to 64,000 bytes. Longer text is cut after redaction, and the sample records which fields were cut.
Group multi-turn conversations
Set two metadata keys:
session_id: one conversationturn_id: one turn (reuse across a tool round-trip)
Each model call is its own row. A turn that runs model → tool → model spans two rows; reuse turn_id to group them.
with sp.metadata({
"session_id": conversation_id,
"turn_id": turn_id,
"customer_id": "cust_acme_123",
}):
first = client.chat.completions.create(...)
# run tool locally, then:
final = client.chat.completions.create(...)Narrow capture from the SDK
SDK helpers can only reduce capture, never enable it:
sp.configure_content_capture(
allow=True, # False vetoes all capture from this process
record_prompt=True, # False drops inputs
record_outcome=True, # False drops outputs
sample_rate_multiplier=1.0,
)import { configureContentCapture } from "@superpenguin/js";
configureContentCapture({
allow: true,
recordPrompt: true,
recordOutcome: true,
sampleRateMultiplier: 1,
});