Re-identification
Re-identification is the governed, audited process of reversing a cryptographic redaction to recover the original value. It is available only for values that were redacted using the CRYPTO_REPLACE or FPE_ENCRYPT_REPLACE strategies, which produce mathematically reversible replacements.
Requires authorization. Every re-identification call requires a valid API key and a stated reason. Every reversal is recorded in the audit log.
When re-identification is possible
Not all redaction strategies support reversal. Re-identification works only when the original value was replaced using one of these two strategies:
| Strategy | How it works |
|---|---|
CRYPTO_REPLACE |
The original value is encrypted with AES-256-GCM using a key stored in the policy. The replacement is the Base64-encoded ciphertext wrapped in double braces: {{<base64>}}. |
FPE_ENCRYPT_REPLACE |
The original value is encrypted using FF3-1 format-preserving encryption. The replacement looks like the original (digits stay digits, letters stay letters). |
Strategies that produce random or static replacements (such as REDACT, RANDOM_REPLACE, or HASH_SHA256_REPLACE) cannot be reversed through this endpoint. If you need to recover those originals, consult the Redaction Ledger, which stores the original token alongside its replacement for every redaction in a ledger-enabled context.
Making a re-identification request
To reverse chosen values, send a POST request to /api/reidentify with a JSON body. To restore every CRYPTO_REPLACE value in a text, see Re-identifying a text.
{
"values": ["<encrypted-value-1>", "<encrypted-value-2>"],
"strategy": "CRYPTO_REPLACE",
"policyName": "my-policy",
"reason": "Patient care, authorized by Dr. Smith"
}
| Field | Required | Description |
|---|---|---|
values |
Yes | One or more replacement values to reverse. |
strategy |
Yes | CRYPTO_REPLACE or FPE_ENCRYPT_REPLACE. |
policyName |
Required for CRYPTO_REPLACE; optional for FPE_ENCRYPT_REPLACE |
The policy whose cryptographic parameters were used during redaction. For FPE_ENCRYPT_REPLACE, omit this field to use your account's default key and derived tweak; supply it when the policy specified its own fpe configuration. |
reason |
Yes | A free-text statement of why the reversal is authorized. Recorded verbatim in the audit log. |
Example: CRYPTO_REPLACE
curl -s -X POST https://philter:8080/api/reidentify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": ["k7Xv2...base64ciphertext...=="],
"strategy": "CRYPTO_REPLACE",
"policyName": "my-policy",
"reason": "Patient care, authorized by Dr. Smith"
}'
Example: FPE_ENCRYPT_REPLACE (account default key)
curl -s -X POST https://philter:8080/api/reidentify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": ["750918814058654607"],
"strategy": "FPE_ENCRYPT_REPLACE",
"reason": "Fraud investigation case #4421"
}'
Example: FPE_ENCRYPT_REPLACE (policy key and tweak)
If the policy that produced the redaction specified its own fpe configuration, pass policyName so Philter uses its key and tweak unchanged. Account defaults apply only when the policy has no fpe object. An incomplete policy configuration does not fall back to account defaults.
Reversal requires the same key and tweak used for the original redaction. FPE does not authenticate ciphertext: different valid parameters can yield incorrect plaintext without a decryption error.
For example:
curl -s -X POST https://philter:8080/api/reidentify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": ["750918814058654607", "018989839189395384"],
"strategy": "FPE_ENCRYPT_REPLACE",
"policyName": "my-policy",
"reason": "Fraud investigation case #4421"
}'
Authorization
A user may re-identify their own values. An admin may re-identify values belonging to any user by supplying that user's username in the owner query parameter, which requires ADMIN_CROSS_USER_ACCESS_ENABLED=true:
curl -s -X POST "https://philter:8080/api/reidentify?owner=other-user" \
-H "Authorization: Bearer ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": ["k7Xv2...base64ciphertext...=="],
"strategy": "CRYPTO_REPLACE",
"policyName": "my-policy",
"reason": "Compliance audit"
}'
Unauthenticated callers receive 401 Unauthorized. A non-admin who names another user as owner receives 404 Not Found (same behavior as other endpoints that support the owner parameter).
Response
The response contains one result per input value, in the same order:
{
"results": [
{
"encrypted": "<encrypted-value-1>",
"decrypted": "John Smith"
},
{
"encrypted": "<encrypted-value-2>",
"error": "Decryption failed."
}
]
}
A successful reversal has decrypted set and no error field. A failed reversal has error set (for example, if the value was tampered with or encrypted with a different key) and no decrypted field. The overall HTTP status is 200 even when individual values fail; inspect each result's error field.
HTTP status codes
| Code | Meaning |
|---|---|
200 OK |
Request accepted. Check each result's error field for per-value failures. |
400 Bad Request |
Invalid request: values is empty, reason is blank, strategy is unrecognized, policyName is missing for CRYPTO_REPLACE, the named policy has no crypto key configured, or the policy's key names an environment variable not in POLICY_ALLOWED_ENV_KEYS. |
401 Unauthorized |
The Authorization header is absent or the API key is not recognized. |
403 Forbidden |
The API key does not have the reidentify scope. |
404 Not Found |
The specified policyName does not exist, or a non-admin caller supplied an owner that is not their own account. |
500 Internal Server Error |
An unexpected server-side error occurred. |
Re-identifying a text
POST /api/reidentify/text takes a text, such as a redacted document or an LLM's response to a redacted prompt, and returns it with each CRYPTO_REPLACE value restored. Philter finds every {{...}} token in the text, decrypts it with the named policy's crypto key, and puts the original value in its place.
curl -s -X POST https://philter:8080/api/reidentify/text \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Write to {{k7Xv2...base64ciphertext...==}} about the claim.",
"policyName": "my-policy",
"reason": "Restore LLM response for authorized claims review"
}'
| Field | Required | Description |
|---|---|---|
text |
Yes | The text to restore. |
policyName |
Yes | The policy whose crypto key was used during redaction. |
reason |
Yes | A free-text statement of why the reversal is authorized. Recorded verbatim in the audit log. |
The response carries the restored text and how many tokens were found, restored, and left unchanged:
{
"text": "Write to jane.doe@example.com about the claim.",
"found": 1,
"restored": 1,
"failed": 0
}
Limits:
- Only
CRYPTO_REPLACEtokens are restored.FPE_ENCRYPT_REPLACEreplacements look like ordinary data and cannot be told apart from it in a text, so reverse them by value with/api/reidentify. Other strategies cannot be reversed. - A token that does not decrypt is left in the text as it is and counted in
failed. This includes a token encrypted under another policy's key and one whose ciphertext was altered after redaction, for example shortened by an LLM. - A token altered so that it is no longer
{{followed by Base64 and}}, for example split by a space or missing a brace, is not found, so it is counted in neitherfoundnorfailed. A model's response may therefore come back partly restored withfailedat0; check the result rather than the counts alone. - The request body is limited by
MAX_FILE_SIZE_BYTES_OTHER.
Authorization, the owner parameter, and the status codes are the same as for /api/reidentify, except that policyName is always required and text takes the place of values. The response is 200 even when some tokens fail.
Audit trail
Every call to /api/reidentify or /api/reidentify/text produces an audit event of type redaction_reversed, whether or not all values succeed. The event records:
- Who: the API key (and therefore the user) that made the call.
- What: the list of encrypted input values (ciphertexts), the strategy used, and how many reversals succeeded. For a text, the tokens found in it, never the text.
- When: the timestamp of the event.
- Authority: the
reasonfield supplied in the request, verbatim.
If an admin used the owner parameter, the affected user's id is also recorded. See Auditing → Re-identification for the full event specification.
Security considerations
- The
reasonfield is the caller's stated authority. Philter records it but does not validate it. Enforce authorization policies at the organizational level. - The encrypted input values (ciphertexts) are recorded in the audit log. The decrypted originals are never written to the audit log.
- Re-identification endpoints should be accessible only to systems and users that genuinely need reversal capability. Consider restricting access at the network or API gateway level in addition to API key authorization.
See also
- Redaction Policies: how
CRYPTO_REPLACEandFPE_ENCRYPT_REPLACEare configured in a policy. - Redaction Ledgers: for recovering originals from non-cryptographic redaction strategies.
- Auditing: the full list of audit events.