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 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 headerx-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:
| Response | SDK behavior | What to do |
|---|---|---|
429 rpm_exceeded / tpm_exceeded | Retries after retry-after (seconds left in the window). | Let it — the retry lands in the next window. |
429 daily_budget_exceeded | Retries too — uselessly, the budget stays spent until 00:00 UTC. | Branch on error.code and stop retrying; raise the budget instead. |
503 no_capacity | Retries after retry-after: 10. | Let it. |
402 insufficient_credits | Not retried. | Top up, then retry yourself. |
| Network timeout on a non-stream call | Retries — 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.