Skip to content

Idempotency

A network timeout leaves you not knowing whether a request ran. Retrying without protection can bill you twice for the same work. The Idempotency-Key header makes non-streaming retries safe: the same key returns the same response, billed once.

How to use it

Send an Idempotency-Key header on POST /v1/chat/completions with stream: false. The key is any string you generate (≤255 characters), unique per request within your organization; keys are kept for 24 hours.

curl
curl https://api.clfaigateway.dev/v1/chat/completions \
  -H "Authorization: Bearer sk-gw-..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-42-summary-1" \
  -d '{
    "model": "kimi-k2.6",
    "messages": [{"role": "user", "content": "Summarize order 42."}],
    "max_tokens": 256
  }'

Exact semantics

  • First completion wins: when a request finishes with a 2xx, its response is stored under the key.
  • Same key, same body again: the stored response is replayed verbatim — same body, original x-request-id — with the header x-gw-idempotent-replay: true. The model does not run again and nothing new is billed.
  • Same key while the first attempt is still running: 409 idempotency_key_in_flight — wait for the first attempt to finish, then retry.
  • Same key, different body: 422 idempotency_key_reused — a key identifies one logical request; use a fresh key for a different one.
  • Only 2xx responses are stored. Errors are never replayed, so a failed attempt does not pin its error to the key. After fixing a rejected request, send the corrected body with a fresh key.

Why streams are excluded

A streaming request with an Idempotency-Key is rejected with 400. A stream is a sequence of events delivered in real time — a stored "replay" could not faithfully reproduce it (especially if the original was cut mid-flight), and recording every stream in full just in case would be storage waste priced into everyone's requests. We would rather refuse than pretend.

For streams, each attempt is a separate request billed for what it actually delivered — cap the worst case with max_tokens and read the policy on Billing.

SDK auto-retries

The official OpenAI SDKs (Python and Node) silently retry 429 and 5xx responses — by default twice, with exponential backoff, honoring retry-after. Each retry is a new request with a new x-request-id. What that means here:

ResponseSDK behaviorWhat to do
429 rpm_exceeded / tpm_exceededRetries after retry-after (seconds left in the window).Let it — the retry lands in the next window.
429 daily_budget_exceededRetries too — uselessly, the budget stays spent until 00:00 UTC.Branch on error.code and stop retrying; raise the budget instead.
503 no_capacityRetries after retry-after: 10.Let it.
402 insufficient_creditsNot retried.Top up, then retry yourself.
Network timeout on a non-stream callRetries — this is the double-billing risk.Send an Idempotency-Key so the retry replays instead of re-running.

Ledger-level protection is always on

Independently of this header, each request settles against your balance exactly once — retries by our own infrastructure can never double-settle a request. The Idempotency-Key protects you at one level up: when your client sends the same logical request twice.