> For the complete documentation index, see [llms.txt](https://docs.toolkit.equs.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.toolkit.equs.ai/docs/crypto/decrypt-payload-api.md).

# Decrypt Payload (API)

Decrypt data you encrypted to a Holder's key from your back end, without the Holder present, and get the result back encrypted to you

You can keep data encrypted to a Holder's key and still use it from your back end, with no Holder interaction. You send the ciphertext to the platform on the Holder's behalf, the Holder's wallet decrypts it with a key that never leaves the wallet, and what comes back is re-encrypted to a key only you hold. The plaintext is never exposed on the way.

This is the server-to-server counterpart of [Decrypt Payload (Embed UI)](/docs/crypto/decrypt-payload-embed-ui.md), where the Holder approves each decryption in their Web Wallet.

{% hint style="info" %}
This uses the **Web Wallet**, which is off by default and available on request.
{% endhint %}

{% hint style="warning" %}
**The platform acts on the Holder ID you send.** These calls are authorized by your Tenant's credentials, not by the Holder, so your back end is responsible for decrypting only your own users' data.
{% endhint %}

**Before you start** you need:

* The Holder onboarded with a [dedicated DID](/docs/crypto/dedicated-did.md), which gives them the key-agreement key you encrypt to.
* Your Tenant's client credentials. See [Authentication](/docs/api/authentication.md).

{% hint style="info" icon="rectangle-api" %}
All calls mentioned on this page are on the [Web Custodial Wallet API](https://docs.toolkit.equs.ai/docs/crypto/pages/MvAvsnLQU3ER2hOIIDU2#id-2.4.1-apis).
{% endhint %}

### How it works ⚙️

1. **Ahead of time**, encrypt the data (or a symmetric key that protects it) to the Holder's key-agreement public key, as a compact JWE. The key is in the Holder's DID document; see [Dedicated DID](/docs/crypto/dedicated-did.md).
2. **When you need it**, generate an ephemeral P-256 key pair.
3. **Call the platform** with the JWE, the public half of your ephemeral key as `senderEpk`, and the Holder's ID as `userId`: the user's ID, or the `agentId` for an agent.
4. **Decrypt the result** with the private half. It's the plaintext, re-encrypted as a JWE to your ephemeral key.

### Payload requirements 📋

The JWE's protected header must carry all of these:

| Header       | Value                                                        |
| ------------ | ------------------------------------------------------------ |
| `alg`        | `ECDH-ES` or `ECDH-ES+A256KW`                                |
| `enc`        | `A256GCM`                                                    |
| `kid`        | The ID of the Holder's current key-agreement key             |
| `epk`        | The encryptor's P-256 ephemeral public key                   |
| `apu`, `apv` | Non-empty base64url values, the same ones used at encryption |

{% hint style="warning" %}
**Many JWE libraries leave out `apu` and `apv` by default**, and a payload without them is rejected with `invalid_jwe_parameters`. In `jose`, set them with `setKeyManagementParameters({ apu, apv })`.
{% endhint %}

`senderEpk` is your ephemeral public key as a P-256 JWK: `kty` `EC`, `crv` `P-256`, and the `x` and `y` coordinates. If you give it a `kid`, the platform echoes it in the result's header.

The result is a compact JWE with `alg` `ECDH-ES+A256KW` and `enc` `A256GCM`. Its header carries only `alg`, `enc`, and your `kid` if you set one.

### Decrypt one payload 🔓

## Decrypt a holder payload (Service Provider / Tenant)

> Endpoint for Service Provider or Tenant. Decrypts a compact JWE on behalf of the holder named by userId and returns the plaintext re-encrypted to the supplied senderEpk.

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Web Custodial Wallet Documentation","version":"0.1.2"},"tags":[{"name":"Decryption","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"CreateDecryptionRequestRequest":{"type":"object","properties":{"payload":{"type":"string","description":"Compact JWE encrypted to the holder keyAgreement public key"},"senderEpk":{"description":"Ephemeral P-256 public JWK the result is encrypted to","allOf":[{"$ref":"#/components/schemas/SenderEphemeralPublicKey"}]},"userId":{"type":"string","description":"User unique ID"}},"required":["payload","senderEpk","userId"]},"SenderEphemeralPublicKey":{"type":"object","properties":{"kty":{"type":"string","description":"Key type of the sender ephemeral public key.","enum":["EC"]},"crv":{"type":"string","description":"Elliptic curve of the sender ephemeral public key.","enum":["P-256"]},"x":{"type":"string","description":"Base64url-encoded X coordinate"},"y":{"type":"string","description":"Base64url-encoded Y coordinate"},"kid":{"type":"string","description":"Key identifier echoed into the result JWE header"}},"required":["kty","crv","x","y"]}}},"paths":{"/api/v1/decryption/requests":{"post":{"description":"Endpoint for Service Provider or Tenant. Decrypts a compact JWE on behalf of the holder named by userId and returns the plaintext re-encrypted to the supplied senderEpk.","operationId":"DecryptionController_decrypt_api/v1","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDecryptionRequestRequest"}}}},"responses":{"200":{"description":"Payload decrypted and re-encrypted to senderEpk"},"400":{"description":"- Request validation failed; `message` lists the offending fields\n- `invalid_jwe_parameters` (in `code`) — the JWE protected header is missing or declares an unsupported `alg`, `enc`, `kid`, `epk`, `apu`, `apv` or `encrypted_key`\n- `payload_kid_mismatch` — the payload was encrypted to a key other than the holder current keyAgreement key\n- `decryption_failed` (with `code: DECRYPTION`) — the payload could not be decrypted. Also returned when the key store itself fails"},"401":{"description":"Missing or invalid agents-realm service token"},"404":{"description":"- `no_did` — no holder identity exists for this user\n- `no_key_agreement` — the holder DID document has no usable keyAgreement verification method\n- `key_agreement_kid_missing` — the holder keyAgreement method exposes no kid\n- `key_unavailable` — the holder key could not be resolved from the key store"},"500":{"description":"- `key_unavailable` — the holder key could not be resolved from the key store\n- `unexpected_error` — unhandled failure"}},"summary":"Decrypt a holder payload (Service Provider / Tenant)","tags":["Decryption"]}}}}
```

### Decrypt many payloads 📦

Send from 1 to 100 payloads in one call, each for its own Holder. Items are processed independently and the results come back in the order you sent them, so one failing item doesn't stop the rest.

The call returns `200` even when some items fail, so check each result for its own status. If the platform is saturated, the whole batch is rejected with `503 service_overloaded` and nothing is decrypted. Retry with exponential backoff.

## Decrypt multiple payloads (Service Provider / Tenant)

> Endpoint for Service Provider or Tenant. Decrypts a batch of compact JWEs, each on behalf of the user named by its userId, and returns every plaintext re-encrypted to its supplied senderEpk. Items are processed independently; a failing item is reported without aborting the batch.

```json
{"openapi":"3.0.0","info":{"title":"EqusDTK Web Custodial Wallet Documentation","version":"0.1.2"},"tags":[{"name":"Decryption","description":""}],"security":[{"sp":[]},{"tenant":[]},{"sp":[]}],"components":{"securitySchemes":{"sp":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Service Provider. Token of the Service Provider client in the `agents` realm, with no `tenantId` claim. Authorize one bearer scheme at a time."},"tenant":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Tenant. Token of a Tenant machine client or of a Tenant portal user, carrying the `tenantId` claim. Authorize one bearer scheme at a time."}},"schemas":{"CreateDecryptionRequestsRequest":{"type":"object","properties":{"requests":{"description":"Decryption requests to process","type":"array","items":{"$ref":"#/components/schemas/CreateDecryptionRequestRequest"}}},"required":["requests"]},"CreateDecryptionRequestRequest":{"type":"object","properties":{"payload":{"type":"string","description":"Compact JWE encrypted to the holder keyAgreement public key"},"senderEpk":{"description":"Ephemeral P-256 public JWK the result is encrypted to","allOf":[{"$ref":"#/components/schemas/SenderEphemeralPublicKey"}]},"userId":{"type":"string","description":"User unique ID"}},"required":["payload","senderEpk","userId"]},"SenderEphemeralPublicKey":{"type":"object","properties":{"kty":{"type":"string","description":"Key type of the sender ephemeral public key.","enum":["EC"]},"crv":{"type":"string","description":"Elliptic curve of the sender ephemeral public key.","enum":["P-256"]},"x":{"type":"string","description":"Base64url-encoded X coordinate"},"y":{"type":"string","description":"Base64url-encoded Y coordinate"},"kid":{"type":"string","description":"Key identifier echoed into the result JWE header"}},"required":["kty","crv","x","y"]}}},"paths":{"/api/v1/decryption/requests/batch":{"post":{"description":"Endpoint for Service Provider or Tenant. Decrypts a batch of compact JWEs, each on behalf of the user named by its userId, and returns every plaintext re-encrypted to its supplied senderEpk. Items are processed independently; a failing item is reported without aborting the batch.","operationId":"DecryptionController_decryptMany_api/v1","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDecryptionRequestsRequest"}}}},"responses":{"200":{"description":"Per-item decryption results, in request order; each entry reports success or an error code. A failed item never changes the HTTP status"},"400":{"description":"Request validation failed (e.g. an empty list, more than 100 items, or an invalid item)"},"401":{"description":"Missing or invalid agents-realm service token"},"500":{"description":"`unexpected_error` — unhandled failure"},"503":{"description":"`service_overloaded` — the instance is saturated and is shedding batch requests. The whole batch is rejected, nothing was decrypted; retry with exponential backoff"}},"summary":"Decrypt multiple payloads (Service Provider / Tenant)","tags":["Decryption"]}}}}
```

### Errors 🚨

| Error                       | What it means                                                                                                                     |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_jwe_parameters`    | The JWE header is missing a required parameter, or declares an unsupported one. See [Payload requirements](#payload-requirements) |
| `payload_kid_mismatch`      | The payload was encrypted to a key other than the Holder's current key-agreement key                                              |
| `decryption_failed`         | The payload couldn't be decrypted, for example because it's malformed or was encrypted to another key                             |
| `no_did`                    | The Holder has no dedicated DID yet. Onboard them first                                                                           |
| `no_key_agreement`          | The Holder's DID document has no usable key-agreement key                                                                         |
| `key_agreement_kid_missing` | The Holder's key-agreement key has no ID                                                                                          |
| `key_unavailable`           | The Holder's key couldn't be retrieved                                                                                            |

### With the Holder present 🙋

To have the Holder approve each decryption in their Web Wallet instead, use [Decrypt Payload (Embed UI)](/docs/crypto/decrypt-payload-embed-ui.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.toolkit.equs.ai/docs/crypto/decrypt-payload-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
