Skip to content

Chat completions

POST /api/v1/chat/completions accepts OpenAI-compatible chat-completions payloads and supports streaming (Server-Sent Events) and Idempotency-Key semantics that stop a completed call from running or billing twice. A stock OpenAI SDK pointed at https://api.cloakapi.io/api/v1 is not sufficient by itself: the gateway also requires client-side tokenisation and proof headers. Use a CloakAPI SDK or the local proxy, or implement the proof flow shown below in your own client.

X-Cloak-Pretokenised

Setting this header is an assertion you make about your own request body. The gateway verifies the tenant-bound MAC over the bytes it receives; it does not verify that a tokeniser ran, that every identifier was detected, or that the body contains no raw PII.

The gateway requires the proof headers below. A request without them is refused and nothing is sent upstream: no engine identity → 428 engine_identity_required or 403 engine_untrusted; no pre-tokenised proof → 428 pretokenised_required; a MAC that does not match the bytes → 403 pretokenised_mac_invalid. The local proxy and the SDKs send all of this for you. A successful check authenticates the submitted bytes and declared identity; it does not inspect message content or establish that all PII was replaced. Calling the endpoint by hand looks like this:

Terminal window
# $BODY must already be tokenised on your device, by an official CloakAPI engine.
BODY='{"model":"claude-haiku-4-5-20251001","messages":[{"role":"user","content":"Hello"}]}'
# 1. MAC over the exact bytes you will send (tenant-bound, from your key)
MAC=$(curl -sS -X POST https://api.cloakapi.io/api/v1/pretokenised/mac \
-H "Authorization: Bearer $CLOAKAPI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary "$BODY" | jq -r .mac)
# 2. Send the SAME bytes with the proof and the engine identity
curl https://api.cloakapi.io/api/v1/chat/completions \
-H "Authorization: Bearer $CLOAKAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Cloak-Pretokenised: 1" \
-H "X-Cloak-Client-MAC: $MAC" \
-H "X-Cloak-Engine-Version: $CLOAK_ENGINE_VERSION" \
-H "X-Cloak-Engine-Hash: $CLOAK_ENGINE_HASH" \
--data-binary "$BODY"

$CLOAK_ENGINE_VERSION and $CLOAK_ENGINE_HASH are the version and binary sha256 of the official CloakAPI engine that tokenised $BODY (minimum version 1.6.0; the accepted hashes are listed at GET https://api.cloakapi.io/api/v1/engine/hashes). Send the body with --data-binary, byte for byte: any change after the MAC was computed makes it invalid.

Idempotency-Key

A flaky connection that triggers a client-side retry must not double-bill the user or duplicate the receipt-chain entry. On this endpoint the gateway does not keep the response body: a retry with the same key and body is refused with 409, not replayed (see the contract below). CloakAPI implements the IETF-draft Idempotency-Key header (draft-ietf-httpapi-idempotency-key-header).

Add the header to the pre-tokenised call above ($BODY and $MAC as computed there):

Terminal window
curl https://api.cloakapi.io/api/v1/chat/completions \
-H "Authorization: Bearer $CLOAKAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Cloak-Pretokenised: 1" \
-H "X-Cloak-Client-MAC: $MAC" \
-H "X-Cloak-Engine-Version: $CLOAK_ENGINE_VERSION" \
-H "X-Cloak-Engine-Hash: $CLOAK_ENGINE_HASH" \
-H "Idempotency-Key: $(uuidgen)" \
--data-binary "$BODY"

Contract

AspectBehaviour
Header nameIdempotency-Key
Recommended formatUUID v4
Max length128 chars
ScopePer organisation (tenant); also bound to the request body — the same key with a different body returns 409
TTL24 hours; an expired key is treated as fresh
What is keptA body-free completion marker and a digest of the request body. The response body (including a streamed transcript) is never retained on chat/completions, messages, embeddings.
Same key, same body, after the first call finished409 idempotency_response_not_retained. No second provider call, no second debit; the original response is not returned again
Same key, different body409 idempotency_key_conflict
Same key, same body, first call still runningThe duplicate waits up to 3 seconds, then gets 409 idempotency_request_in_progress. Retry with the same key later
Failed first callA call that failed before consuming anything leaves the key retryable
Response headersIdempotency-Key-Used, Idempotency-Replayed and X-CloakAPI-Idempotent-Replay are sent as false on the first call. No relay response is ever replayed

Concurrent retries — if the original call is still in flight when a duplicate arrives, the duplicate waits up to 3 seconds and then returns 409 idempotency_request_in_progress. The gateway never starts a second upstream call for the same key.

Receipt / chain semantics — a retry does not run the call again, so the chain sequence does not advance and the user’s balance is not debited a second time. Because the response is not kept, the retry does not return the original receipt either: store the X-CloakAPI-Receipt-V2 header from the first response yourself. To check a receipt you saved:

Terminal window
curl -s -X POST https://api.cloakapi.io/api/v1/receipts/verify \
-H 'Content-Type: application/json' \
-d '{"receipt":"<x-cloakapi-receipt-v2 header from the original response>"}'

Streaming (SSE)

Pass "stream": true to receive an OpenAI-style Server-Sent Events stream. The body is still pre-tokenised and MAC’d exactly as above; the MAC covers the body including "stream": true. (The local proxy 0.3.0 does not stream: it answers "stream": true with HTTP 501.)

Terminal window
BODY='{"model":"claude-haiku-4-5-20251001","messages":[{"role":"user","content":"Count to three"}],"stream":true}'
MAC=$(curl -sS -X POST https://api.cloakapi.io/api/v1/pretokenised/mac \
-H "Authorization: Bearer $CLOAKAPI_API_KEY" \
-H "Content-Type: application/json" \
--data-binary "$BODY" | jq -r .mac)
curl -N https://api.cloakapi.io/api/v1/chat/completions \
-H "Authorization: Bearer $CLOAKAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Cloak-Pretokenised: 1" \
-H "X-Cloak-Client-MAC: $MAC" \
-H "X-Cloak-Engine-Version: $CLOAK_ENGINE_VERSION" \
-H "X-Cloak-Engine-Hash: $CLOAK_ENGINE_HASH" \
--data-binary "$BODY"

Response is Content-Type: text/event-stream; charset=utf-8 with frames of the shape:

data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"1"}}]}
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":" 2"}}]}
data: [DONE]
event: receipt-v2
data: {"alg":"ES256","jti":"...","hash_upstream_response":"...","input_tokens":...,"output_tokens":...,...}

The terminator is data: [DONE]\n\n. After it, the gateway emits a single event: receipt-v2 frame carrying the same receipt envelope you would receive in the X-CloakAPI-Receipt-V2 header for a non-streamed call. Pass the envelope’s data: payload to /api/v1/receipts/verify to confirm the signature, the upstream-response hash, and the chain position — the verifier endpoint does not care whether the receipt originated from a streamed or non-streamed call.

Streaming + Idempotency-Key

Streamed calls honour Idempotency-Key too, with the same contract as the non-streamed path. The gateway does not record the SSE transcript. The key stays reserved while the stream runs; a stream that failed before delivering any frame or billable token leaves the key retryable, while a stream that delivered output (or was billed) is marked done, so a retry cannot be a second free completion.

Terminal window
KEY=$(uuidgen)
curl -N -H "Idempotency-Key: $KEY" ... # first call: real upstream stream
curl -N -H "Idempotency-Key: $KEY" ... # retry after completion: 409 idempotency_response_not_retained, no debit

A different request body with the same key returns 409 idempotency_key_conflict, exactly like the non-streamed path.

Edge buffering

api.cloakapi.io advertises Cache-Control: no-cache, no-store, must-revalidate and X-Accel-Buffering: no on streamed responses, and the front nginx is configured with proxy_buffering off for the chat-completions location. This means individual chunks reach the client as soon as they arrive from the upstream provider — there is no edge-layer buffer to drain.

If you are running CloakAPI behind your own reverse proxy (Cloudflare, AWS ALB, etc.), make sure that proxy also passes SSE through unbuffered. For Cloudflare you typically need a Page Rule with Cache Level: Bypass for /api/v1/chat/completions.

Reference matrix

BehaviourNon-streamStream
Idempotency-Key honouredyesyes
Response replayed on retryno (409 idempotency_response_not_retained)no (same)
Conflict (different body)409 idempotency_key_conflict409 idempotency_key_conflict
Receipt deliveryX-CloakAPI-Receipt-V2 headerevent: receipt-v2 SSE frame
Verifier endpoint/api/v1/receipts/verify/api/v1/receipts/verify
Chain seq advances on retrynono
Balance debited on retrynono

Last updated: