Skip to content

Idempotency

Send a key and a retry replays the stored answer instead of paying twice.

4 min read

The problem it solves

A network drop between the provider finishing and your client receiving is indistinguishable, from your side, from a request that never ran. So the client retries — and without a key the second call has no way to recognise the first, runs the model again, and bills you twice for one answer.

Using it

Send an Idempotency-Key header carrying any string you generate. A UUID per logical operation is the usual choice.

bash
curl https://api.multigrid.ai/v1/chat/completions \
  -H "Authorization: Bearer $MULTIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c9a3e-5b2d-4e77-9f10-2c8a41d7b0e5" \
  -d '{"model": "openai/gpt-5-nano", "messages": [{"role": "user", "content": "…"}]}'

The claim is taken before anything can reach a provider, by inserting a row whose primary key is your string. That insert is the lock: of two simultaneous retries exactly one wins it, atomically, with no extra column and no transaction. Checking first and writing afterwards is precisely what allowed both to bill.

Keys are namespaced by account, so your retry-1 can neither replay nor collide with anybody else’s.

The three outcomes

OutcomeDescription
first useThe claim is yours. The request runs normally and its response body is stored against the key.
replayA previous request with this key already finished. You get its stored body, its original request id, and X-Multigrid-Idempotent-Replay: true. Nothing is charged.
in flightSomeone else holds the claim and is still upstream. You get 409 request_in_flight with Retry-After: 1. Retrying with backoff collects the result — the alternative is running the model a second time and billing for both, which is the exact thing the header was sent to prevent.
A failed request does not lock you out
Every exit that stored no response — a 402, a 404 on the model, an upstream failure — hands the key back immediately, so an honest retry is not blocked. None of those paths charged anything, so there is nothing to double-bill. A process that dies mid-flight releases its claim after about five minutes, so a crash cannot pin a key forever.

Streams are excluded

A request with stream: true ignores the header. An SSE body cannot be meaningfully replayed from storage, and re-running the model would be the double charge the header exists to prevent — so instead of pretending, the claim is simply not taken.

Lifetime

A completed key stays replayable for 24 hours, as with Stripe, then is swept. After that the same string is a fresh key and the request runs again.

Something here disagrees with what the API actually did? That is a bug in this page, and worth reporting.

Report it