Deep Decrypt
Recursively decrypt all fields in a nested JSON object using the Cipherion API
Deep Decrypt
deep_decrypt is the inverse of deep_encrypt. It recursively traverses an encrypted JSON object or array and restores every encrypted leaf value to its original plaintext — producing an output that is structurally and semantically identical to what was originally passed to /deep_encrypt.
Pass the encrypted object from a /deep_encrypt response directly as the encrypted field in your request body. The passphrase must match the one used during encryption.
Endpoint
POST /deep_decrypt/:projectIdHow It Works
When you send an encrypted payload to /deep_decrypt, the API applies the same recursive traversal as encryption — but in reverse:
- Encrypted leaf values (strings containing encrypted packages) are decrypted and replaced with their original plaintext values.
- Nested objects are traversed recursively, descending into each level until leaf values are reached and decrypted.
- Arrays are processed element by element. Objects inside arrays are recursively decrypted. Encrypted primitive elements are decrypted individually, with ordering preserved.
- Plaintext fields — those that were excluded during encryption — are passed through unchanged. If you do not also exclude them during decryption, the API will attempt to decrypt them and fail. Always mirror your exclusions (see Use the Same Exclusions below).
nullandundefinedvalues are passed through unchanged, matching the behavior of/deep_encrypt.
Decryption Response Flow
The typical server-side flow looks like this:
- Retrieve the encrypted object from storage, a queue, or a downstream service response.
- Send it to
/deep_decryptwith the same passphrase and exclusion options used during encryption. - Read
data.datafrom the response — this is your restored, fully decrypted payload. - Use the restored payload in your application logic.
Encrypted DB record
│
▼
POST /deep_decrypt
│
▼
Cipherion API
(recursive traversal + decryption)
│
▼
response.data.data → original plaintext objectRequest
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Your project identifier |
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your Cipherion API key |
Content-Type | Yes | Must be application/json |
Body
| Parameter | Type | Required | Description |
|---|---|---|---|
encrypted | object | Yes | The encrypted JSON object or array to decrypt recursively |
passphrase | string | Yes | Secure passphrase configured in the Cipherion dashboard |
exclude_fields | array | No | Exact field paths to skip during decryption |
exclude_patterns | array | No | Wildcard-based field name patterns to skip |
fail_gracefully | boolean | No | Whether to continue when individual fields fail to decrypt (default: false) |
The fail_gracefully Parameter
This flag controls what happens when the API encounters a field it cannot decrypt — for example, a field whose value is plaintext (because it was excluded during encryption), or a field with corrupted encrypted data.
When fail_gracefully is false (the default), the entire operation fails immediately if any single field cannot be decrypted.
Set fail_gracefully to true when:
- The payload contains mixed encrypted and unencrypted fields (e.g. fields excluded during encryption)
- The encrypted data is partially corrupted or malformed
| Value | Behaviour |
|---|---|
true | Fields that fail to decrypt are left in their encrypted form and listed in meta.failed_fields. The rest of the object is returned decrypted. |
false (default) | The entire operation fails immediately with a 400 error if any field cannot be decrypted. |
Use the Same Exclusions
When you excluded fields during encryption — via exclude_fields or exclude_patterns — those fields remain as plaintext in the encrypted payload. If you pass that payload to /deep_decrypt without the same exclusions, the API will attempt to decrypt those plaintext values and fail.
Always mirror the exclusion options from encryption in your decryption request.
// Encryption request (excluded user_id and *_at fields)
{
"data": { ... },
"passphrase": "your-secure-passphrase",
"exclude_patterns": ["user_id", "*_at"]
}
// Decryption request — MUST use the same exclusions
{
"encrypted": { ... },
"passphrase": "your-secure-passphrase",
"exclude_patterns": ["user_id", "*_at"]
}A practical approach is to store the exclusion options alongside the encrypted payload in your database, so they are always available at decryption time.
Example Requests
Basic Example
Method: POST
URL: https://api.cipherion.in/api/v1/crypto/deep_decrypt/YOUR_PROJECT_ID
Headers:
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (raw JSON):
{
"encrypted": {
"name": "a3f8d9e2c1....Encrypted Package",
"user_id": "b2c9e1f3a4d....Encrypted Package"
},
"passphrase": "your-secure-passphrase"
}{
"encrypted": {
"name": "a3f8d9e2c1....Encrypted Package",
"user_id": "b2c9e1f3a4d....Encrypted Package"
},
"passphrase": "your-secure-passphrase"
}curl -X POST https://api.cipherion.in/api/v1/crypto/deep_decrypt/YOUR_PROJECT_ID \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"encrypted": {
"name": "a3f8d9e2c1....Encrypted Package",
"user_id": "b2c9e1f3a4d....Encrypted Package"
},
"passphrase": "your-secure-passphrase"
}'Advanced Example — With Exclusions and Graceful Failure
Method: POST
URL: https://api.cipherion.in/api/v1/crypto/deep_decrypt/YOUR_PROJECT_ID
Headers:
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (raw JSON):
{
"encrypted": {
"orders": [{
"order_id": "ORD-1",
"items": [{
"product_id": "P1",
"name": "351b4c79d0....Encrypted Package",
"price": "c0e93b516997....Encrypted Package"
}]
}]
},
"passphrase": "your-secure-passphrase",
"exclude_fields": ["orders[0].items[0].name"],
"exclude_patterns": ["order_id", "product_id"],
"fail_gracefully": true
}{
"encrypted": {
"orders": [{
"order_id": "ORD-1",
"items": [{
"product_id": "P1",
"name": "351b4c79d0....Encrypted Package",
"price": "c0e93b516997....Encrypted Package"
}]
}]
},
"passphrase": "your-secure-passphrase",
"exclude_fields": ["orders[0].items[0].name"],
"exclude_patterns": ["order_id", "product_id"],
"fail_gracefully": true
}curl -X POST https://api.cipherion.in/api/v1/crypto/deep_decrypt/YOUR_PROJECT_ID \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"encrypted": {
"orders": [{
"order_id": "ORD-1",
"items": [{
"product_id": "P1",
"name": "351b4c79d0....Encrypted Package",
"price": "c0e93b516997....Encrypted Package"
}]
}]
},
"passphrase": "your-secure-passphrase",
"exclude_fields": ["orders[0].items[0].name"],
"exclude_patterns": ["order_id", "product_id"],
"fail_gracefully": true
}'Decrypting a Server-Side API Response
A typical pattern on the server is to retrieve an encrypted record from storage and decrypt it before returning it to a client or processing it further. Here is what the full round-trip looks like:
Encrypted record retrieved from storage:
{
"order_id": "ORD-4821",
"customer_id": "CUST-991",
"created_at": "2025-11-01T09:00:00Z",
"billing": {
"card_number": "a3f8d9e2c1....Encrypted Package",
"card_holder": "b2e7f1a3c4....Encrypted Package",
"expiry": "c9d0e1f2a3....Encrypted Package",
"cvv": "d1e2f3a4b5....Encrypted Package"
},
"shipping": {
"address": "e2f3a4b5c6....Encrypted Package",
"city": "f3a4b5c6d7....Encrypted Package",
"zip": "a4b5c6d7e8....Encrypted Package"
},
"items": [
{ "product_id": "P-01", "name": "b5c6d7e8f9....Encrypted Package", "price": "c6d7e8f9a0....Encrypted Package" },
{ "product_id": "P-02", "name": "d7e8f9a0b1....Encrypted Package", "price": "e8f9a0b1c2....Encrypted Package" }
]
}Cipherion API request to decrypt it (mirroring the original exclusions):
{
"encrypted": { ...the record above... },
"passphrase": "your-secure-passphrase",
"exclude_patterns": ["order_id", "customer_id", "*_at"],
"exclude_fields": ["items[0].product_id", "items[1].product_id"],
"fail_gracefully": true
}Restored response (data.data):
{
"order_id": "ORD-4821",
"customer_id": "CUST-991",
"created_at": "2025-11-01T09:00:00Z",
"billing": {
"card_number": "4111111111111111",
"card_holder": "John Doe",
"expiry": "12/27",
"cvv": "389"
},
"shipping": {
"address": "742 Evergreen Terrace",
"city": "Springfield",
"zip": "62704"
},
"items": [
{ "product_id": "P-01", "name": "Wireless Keyboard", "price": 79.99 },
{ "product_id": "P-02", "name": "USB Hub", "price": 34.99 }
]
}The payload is fully restored to its original state — identical to what was encrypted.
Decrypting a Response in JavaScript
// 1. Retrieve the encrypted record
const record = await db.orders.findById("ORD-4821");
// 2. Send to Cipherion for decryption
const cipherionRes = await fetch(
`https://api.cipherion.in/api/v1/crypto/deep_decrypt/${process.env.PROJECT_ID}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.CIPHERION_API_KEY,
},
body: JSON.stringify({
encrypted: record,
passphrase: process.env.CIPHERION_PASSPHRASE,
exclude_patterns: ["order_id", "customer_id", "*_at"],
exclude_fields: ["items[0].product_id", "items[1].product_id"],
fail_gracefully: true,
}),
}
);
const { data } = await cipherionRes.json();
// 3. Use the restored plaintext object
const order = data.data;
// 4. Optionally inspect for failed fields
if (data.meta.failed_fields.length > 0) {
console.warn("Some fields could not be decrypted:", data.meta.failed_fields);
}Decrypting a Response in Python
import requests
import os
# 1. Retrieve the encrypted record
record = db.orders.find_by_id("ORD-4821")
# 2. Send to Cipherion for decryption
cipher_response = requests.post(
f"https://api.cipherion.in/api/v1/crypto/deep_decrypt/{os.environ['PROJECT_ID']}",
headers={
"x-api-key": os.environ["CIPHERION_API_KEY"],
"Content-Type": "application/json",
},
json={
"encrypted": record,
"passphrase": os.environ["CIPHERION_PASSPHRASE"],
"exclude_patterns": ["order_id", "customer_id", "*_at"],
"fail_gracefully": True,
},
)
response_body = cipher_response.json()
order = response_body["data"]["data"]
# 3. Check for any failed fields
failed = response_body["data"]["meta"]["failed_fields"]
if failed:
print(f"Warning: could not decrypt fields: {failed}")Responses
200 OK — Success
All encrypted fields are decrypted and returned. Structure is preserved.
{
"success": true,
"statusCode": 200,
"message": "Payload decrypted successfully",
"data": {
"data": {
"name": "John Doe",
"user_id": "12345"
},
"meta": {
"excluded_fields": [],
"excluded_patterns": [],
"failed_fields": [],
"fail_gracefully": true,
"operation": "deep_decrypt"
}
},
"timestamp": "2025-12-24T06:13:08.120Z"
}Response Fields
| Field | Type | Description |
|---|---|---|
data.data | object | The decrypted JSON object, matching the original input structure |
data.meta.excluded_fields | array | Field paths that were skipped during decryption |
data.meta.excluded_patterns | array | Patterns used to skip fields |
data.meta.failed_fields | array | Fields that could not be decrypted (only populated when fail_gracefully is true) |
data.meta.fail_gracefully | boolean | The value of fail_gracefully used for this operation |
data.meta.operation | string | Always "deep_decrypt" |
200 OK — Success with Failed Fields
When fail_gracefully is true and some fields cannot be decrypted, the operation still returns 200. Failed fields remain in their encrypted form, and their paths are listed in failed_fields.
{
"success": true,
"statusCode": 200,
"message": "Payload decrypted successfully",
"data": {
"data": {
"orders": [
{
"order_id": "ORD-1",
"items": [
{
"product_id": "P1",
"name": "351b4c79d0f0....Encrypted Package",
"price": "50"
}
]
}
]
},
"meta": {
"excluded_fields": ["orders[0].items[0].name"],
"excluded_patterns": [],
"failed_fields": ["orders[0].items[0].name"],
"fail_gracefully": true,
"operation": "deep_decrypt"
}
},
"timestamp": "2025-12-24T06:50:19.554Z"
}In this example, orders[0].items[0].name was in exclude_fields, so it was skipped and left encrypted. Its path appears in both excluded_fields and failed_fields.
400 Bad Request — Missing Fields
{
"success": false,
"statusCode": 400,
"message": "Missing required fields: projectId (url), x-api-key header, encrypted, or passphrase",
"data": null,
"timestamp": "2026-05-29T00:00:00.000Z"
}400 Bad Request — Decryption Failure (Non-Graceful Mode)
Returned when fail_gracefully is false and any field fails to decrypt.
{
"success": false,
"statusCode": 400,
"message": "Failed to decrypt field: possible invalid passphrase or corrupted data",
"data": null,
"error": {
"statusCode": 400,
"isOperational": "Failed to decrypt field: possible invalid passphrase or corrupted data"
},
"timestamp": "2025-12-24T06:51:34.399Z"
}401 Unauthorized
{
"success": false,
"statusCode": 401,
"message": "Invalid project or API key",
"data": null,
"error": {
"statusCode": 401,
"isOperational": true
},
"timestamp": "2025-12-24T06:18:26.585Z"
}413 Payload Too Large
{
"success": false,
"statusCode": 413,
"message": "Payload too large: maximum 10000 fields allowed per request (received X).",
"data": null,
"timestamp": "2026-05-29T00:00:00.000Z"
}422 Unprocessable Entity
{
"success": false,
"statusCode": 422,
"message": "Field limit exceeded: your plan allows a maximum of X fields per request...",
"data": null,
"timestamp": "2026-05-29T00:00:00.000Z"
}429 Too Many Requests
{
"success": false,
"statusCode": 429,
"message": "decryption quota exceeded: your plan includes X calls per billing period...",
"data": null,
"timestamp": "2026-05-29T00:00:00.000Z"
}500 Internal Server Error
{
"success": false,
"statusCode": 500,
"message": "Deep decryption failed",
"data": null,
"error": {},
"timestamp": "2026-05-29T00:00:00.000Z"
}Core Concepts
Field Exclusions
Field exclusions allow you to selectively skip certain fields during encryption and decryption, keeping them in plaintext throughout their lifecycle. This is important for fields you need to query, index, or inspect without first decrypting the entire payload.
Why exclude fields?
- Searchability — Database queries require plaintext values. Fields used as lookup keys (IDs, status codes, foreign keys) must remain unencrypted.
- Cost reduction — Only encrypted fields are billable. Excluding metadata, timestamps, and system fields reduces the number of billable fields per request.
- Performance — Fewer fields to encrypt and decrypt means faster round-trips.
- Metadata preservation — Fields like
created_at,updated_at, andversionare often non-sensitive and needed for sorting, filtering, and auditing.
Common use cases for exclusions:
| Field type | Example fields | Reason to exclude |
|---|---|---|
| Identifiers | user_id, order_id, _id | Needed for database lookups |
| Timestamps | created_at, updated_at, deleted_at | Used for sorting, auditing, TTL |
| Status fields | status, state, type | Used for filtering and routing |
| Version fields | __v, version, revision | Used for optimistic locking |
Using exclude_patterns for common field groups:
{
"exclude_patterns": ["_id", "__v", "*_at", "status"]
}The *_at pattern matches any field whose name ends in _at — covering created_at, updated_at, deleted_at, and any custom timestamp fields you add later.
Using exclude_fields for specific paths:
{
"exclude_fields": [
"items[0].product_id",
"items[1].product_id",
"meta.source"
]
}exclude_fields uses array-notation dot paths and targets specific field instances — useful when you want to exclude a field in one array element but encrypt the same field in another.
Example payload showing excluded fields:
Request:
{
"data": {
"user_id": "u_991",
"created_at": "2025-10-01T00:00:00Z",
"email": "alice@example.com",
"ssn": "123-45-6789"
},
"passphrase": "your-secure-passphrase",
"exclude_patterns": ["user_id", "*_at"]
}Encrypted result:
{
"user_id": "u_991",
"created_at": "2025-10-01T00:00:00Z",
"email": "a3f8d9e2c1....Encrypted Package",
"ssn": "b2c9e1f3a4....Encrypted Package"
}user_id and created_at are untouched. email and ssn are encrypted.
Fail Gracefully
The fail_gracefully flag controls how the API responds when it encounters a field it cannot process — such as a plaintext value it was asked to decrypt, or a corrupted encrypted package.
Default behavior (fail_gracefully: false):
The entire operation fails immediately when any single field cannot be decrypted. A 400 error is returned and no partial result is provided. Use this in strict environments where a partial decryption would be worse than a hard failure — for example, when every field in the payload is required for downstream processing.
Graceful behavior (fail_gracefully: true):
Fields that cannot be decrypted are left in their current state (encrypted or plaintext) and their paths are listed in meta.failed_fields. The rest of the payload is decrypted and returned with a 200 status. Use this when:
- Payloads contain mixed encrypted and plaintext fields — for instance, when some fields were excluded during encryption and are therefore still plaintext in the stored record. Without
fail_gracefully: true, the API would fail when it encounters those plaintext values. - Partial data recovery is acceptable — in a data migration or bulk processing scenario where some records may be corrupted or partially encrypted, you want to recover as much data as possible rather than block on every bad record.
- Production resilience — in live systems, a decryption failure on one non-critical field should not break the request for the user.
Typical production pattern:
{
"encrypted": { ... },
"passphrase": "your-secure-passphrase",
"exclude_patterns": ["_id", "*_at"],
"fail_gracefully": true
}After receiving the response, check data.meta.failed_fields. If it contains field paths for sensitive data (e.g. ssn, card_number), treat that as a critical failure and alert accordingly. If it only contains non-sensitive paths, log and continue.
Recursive Processing
Both deep_encrypt and deep_decrypt operate recursively. There is no limit on nesting depth — a field nested ten levels deep is processed the same as a top-level field. You never need to flatten or pre-process your payload.
The traversal is depth-first: the API descends into each value before moving to the next sibling. This means large, deeply nested objects are fully processed in a single request.
Nested Object Handling
Every object value encountered during traversal triggers another level of descent. The API enters the nested object, processes its keys, and continues descending if any value is itself an object. Only when a primitive leaf value is reached does encryption or decryption occur. The key names at every level are preserved exactly.
Array Handling
Arrays are iterated in index order. Each element is processed according to its type:
- Primitive elements (strings, numbers, booleans) are encrypted or decrypted directly.
- Object elements are recursively traversed.
- Nested array elements trigger further recursion.
Array length and ordering are always preserved. Encrypted and decrypted arrays will have the same number of elements in the same order as the input.
Notes
- The
encryptedfield in the request body must be the unmodifiedencryptedobject returned by/deep_encrypt. - Use
fail_gracefully: truewhenever your payload might contain a mix of encrypted and plain-text fields — for instance, when some fields were excluded during encryption. - The
exclude_fieldsandexclude_patternshere should mirror what you used during encryption to ensure the excluded (plain-text) fields are not passed to the decryption logic, which would cause errors.