Cipherion
API Key Reference

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/:projectId

How 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).
  • null and undefined values are passed through unchanged, matching the behavior of /deep_encrypt.

Decryption Response Flow

The typical server-side flow looks like this:

  1. Retrieve the encrypted object from storage, a queue, or a downstream service response.
  2. Send it to /deep_decrypt with the same passphrase and exclusion options used during encryption.
  3. Read data.data from the response — this is your restored, fully decrypted payload.
  4. Use the restored payload in your application logic.
Encrypted DB record


POST /deep_decrypt


  Cipherion API
  (recursive traversal + decryption)


response.data.data   →   original plaintext object

Request

Path Parameters

ParameterTypeRequiredDescription
projectIdstringYesYour project identifier

Headers

HeaderRequiredDescription
x-api-keyYesYour Cipherion API key
Content-TypeYesMust be application/json

Body

ParameterTypeRequiredDescription
encryptedobjectYesThe encrypted JSON object or array to decrypt recursively
passphrasestringYesSecure passphrase configured in the Cipherion dashboard
exclude_fieldsarrayNoExact field paths to skip during decryption
exclude_patternsarrayNoWildcard-based field name patterns to skip
fail_gracefullybooleanNoWhether 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
ValueBehaviour
trueFields 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

FieldTypeDescription
data.dataobjectThe decrypted JSON object, matching the original input structure
data.meta.excluded_fieldsarrayField paths that were skipped during decryption
data.meta.excluded_patternsarrayPatterns used to skip fields
data.meta.failed_fieldsarrayFields that could not be decrypted (only populated when fail_gracefully is true)
data.meta.fail_gracefullybooleanThe value of fail_gracefully used for this operation
data.meta.operationstringAlways "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, and version are often non-sensitive and needed for sorting, filtering, and auditing.

Common use cases for exclusions:

Field typeExample fieldsReason to exclude
Identifiersuser_id, order_id, _idNeeded for database lookups
Timestampscreated_at, updated_at, deleted_atUsed for sorting, auditing, TTL
Status fieldsstatus, state, typeUsed for filtering and routing
Version fields__v, version, revisionUsed 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 encrypted field in the request body must be the unmodified encrypted object returned by /deep_encrypt.
  • Use fail_gracefully: true whenever your payload might contain a mix of encrypted and plain-text fields — for instance, when some fields were excluded during encryption.
  • The exclude_fields and exclude_patterns here 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.

On this page