Idempotency-Key: retrying requests without paying twice
A client-side timeout doesn't mean the request never ran on the server. The Idempotency-Key header solves that without manually checking usage history.
4 min · · Updated · API · reliability
The connection drops mid-request, an HTTP client times out before the model finishes answering — and on the server, the request may have completed and been charged in full. Without protection against retries, the only safe option is to check /v1/usage manually before retrying by hand. Idempotency-Key solves this at the protocol level.
How it works
Attach an Idempotency-Key header with an arbitrary unique string to your request. If another request with the same header value and the same body arrives within 24 hours, the server returns the exact same response without re-running the request or charging twice. This works for POST /v1/chat/completions, /v1/messages and /v1/responses — not for balance top-ups.
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer fetch_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-48213-email-draft" \
-d '{
"model": "gpt-5.6-sol",
"messages": [{"role": "user", "content": "Draft a customer email"}]
}'Repeat this exact curl with the same Idempotency-Key and the same JSON body, and the response comes from the already-completed request — no second call to the model, no second charge.
When you actually need it
A connection cut mid-response. As covered in the errors reference: a mid-response cutoff is almost always a client or network timeout, not a token limit. Even when the connection drops, the gateway reads the provider's response to the end and bills the full generation, while the client never found out. A retry with the same Idempotency-Key will not create a second charge.
Automatic retries in task queues. If a worker crashes after sending a request but before recording the result, a restart will replay the call. Without idempotency that's a double charge for one generation — especially noticeable on requests with a large context.
Flaky networks on mobile clients. A bad connection is a common reason for a user tapping "send" twice. Idempotency-Key on the backend protects against both taps reaching the model as two separate requests.
How to pick the key value
The key needs to be unique per logical request and identical across all of that request's retries. Set it on the specific call, not in the client's default headers (for example, default_headers in the OpenAI SDK) — otherwise the next, different request gets a 422 idempotency_conflict. The simplest approach is generating a UUID once before the first attempt and reusing it across retries of that same request:
import { randomUUID } from "crypto";
async function sendWithRetry(body, maxAttempts = 3) {
const idempotencyKey = randomUUID();
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const resp = await fetch("https://api.zukko.pro/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer fetch_your_key",
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (resp.ok) return resp.json();
} catch (e) {
if (attempt === maxAttempts) throw e;
}
}
}Important detail: idempotencyKey is created once before the retry loop, not inside it — otherwise every attempt becomes a brand-new request again.
The 422 idempotency_conflict error
Send the same Idempotency-Key with a different body, and the server answers 422 with code idempotency_conflict (the metadata, user and stream_options fields don't count toward the comparison):
{
"error": {
"message": "Idempotency-Key already used with a different request body",
"code": "idempotency_conflict"
}
}This guards against the opposite bug: if your code accidentally reuses one key for two different requests (say, a counter collision instead of a UUID), the server won't silently return an unrelated response — it refuses explicitly.
The practical takeaway: don't build Idempotency-Key values from an incremental counter like request-1, request-2 within a session — if the counter ever resets, you'll collide with someone else's request body and get a 422 instead of a normal answer.
What Idempotency-Key does not do
It does not replace response caching and doesn't save money on requests that differ in content — it is literally protection against paying twice for the same request on retry. If you need to avoid paying for repeated-but-separately-timed identical prompts, that's the separate caching feature triggered by the X-Zukko-Cache: true header (or temperature: 0 on models that accept it, not Claude).
It also doesn't extend a response's lifetime indefinitely — the deduplication guarantee holds for 24 hours. Retry a day after the first attempt, and it's a new request with its own charge.
Bottom line
Idempotency-Key is one header that removes an entire class of "did the request actually go through, and I just never got the answer" problems. Generate a key once per logical request, reuse it across retries of the same body, and never reuse it across requests with different content — or you'll get a 422 instead of a result.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- opencode agents: setting up roles, models and toolsA custom agent in opencode is a role, a model and a set of permissions. A working config where a cheap model handles routine work and a stronger one edits code.
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.
- Plan mode in Claude Code: plan first, then edit the codeIn plan mode Claude Code reads your code and proposes a plan but edits nothing until you agree. How to enable it, use it and what it costs.