# Create encrypted chat completion

> Relay an encrypted chat request for a resolved encrypted model and offer.

`POST https://api.numes.ai/api/v1/e2ee/chat/completions`

Source: https://0.0.0.0:3000/docs/api-reference/encrypted-inference/create-encrypted-chat-completion
Authorization: Bearer inference key

The body is ciphertext produced on your device, so send it through an encryption SDK such as the [Tinfoil SDK](/docs/inference/encrypted#direct-sdk-setup), or use the dashboard chat or the CLI. Authenticate with your inference key, [resolve the model](/docs/api-reference/encrypted-inference/resolve-encrypted-model) first, and set these headers from that fresh resolution:

| Header | Value |
| --- | --- |
| `X-Inference-Model` | The public `:e2ee` model ID |
| `X-Inference-Offer-Id`, `X-Inference-Offer-Revision-Id` | The resolved offer |
| `X-Inference-Native-Model` | The resolved native model ID |
| `X-Inference-Profile` | The resolved `transport` |

The SDK adds the encryption headers its profile requires. The response stays encrypted until the SDK decrypts it; for EHBP profiles, preserve the `Ehbp-Response-Nonce` header. `X-Generation-Id` identifies the request in [`GET /generation`](/docs/api-reference/chat/get-generation).

Treat an interrupted body as a failure, even if the decrypted output looked complete. Don't retry automatically, and never fall back to a regular model: once encrypted output starts, the request is billed on the provider's final usage, even if you disconnect. See [Billing and limits](/docs/inference/encrypted#billing-and-limits).

Regular [`POST /chat/completions`](/docs/api-reference/chat/create-chat-completion) rejects `:e2ee` model IDs.

A `503` response with `error.metadata.error_type: "inference_not_dispatched"` confirms that the pinned request was rejected before execution. You may resolve and encrypt one fresh attempt with a new idempotency key and the same encryption policy. The dashboard and CLI handle this automatically. Do not apply this recovery to other errors or interrupted response bodies.

## Headers

- `X-Request-Id` (string; Length 1–128): Correlation ID, not an idempotency key. Inference request IDs recorded with a generation are unique per account. A traffic-admitted request reusing a recorded ID returns 400 invalid_request. Traffic admission rejection takes precedence over this conflict, including for reused IDs; it performs no generation lookup and writes no generation, result or attempt. Rate-limit rejection returns 429 rate_limited with Retry-After; an inference limiter outage retains 500 server.
- `X-Inference-Model` (string; required; Length 1–512)
- `X-Inference-Offer-Id` (string<uuid>; required)
- `X-Inference-Offer-Revision-Id` (string<uuid>; required)
- `X-Inference-Native-Model` (string; required; Length 1–256)
- `X-Inference-Profile` (string; required; Length 1–128)
- `X-Tinfoil-Enclave-Url` (string<uri>; Length ≤ 2,048)
- `X-Encrypted-Client-Key` (string; Pattern ^[0-9a-f]{64}$)
- `Ehbp-Encapsulated-Key` (string; Pattern ^[0-9a-f]{64}$)

## Response 503 (application/json)

- `error` (object; required)
  - `code` (integer; required)
  - `message` (string; required)
  - `metadata` (object)
- `openrouter_metadata` (object)

## Response default (application/json)

- `error` (object; required)
  - `code` (integer; required)
  - `message` (string; required)
  - `metadata` (object)
- `openrouter_metadata` (object)
