Skip to main content
A retry is only safe if the server can tell it apart from a second request. Idempotency-Key is how you tell it. Send the header on a designated POST and Exo records that request’s response for 24 hours. Retry with the same key and you get the stored response back verbatim, without the route running again.

The shape of it

Without the header there is no idempotency handling at all, and a retry after a client timeout produces a second job and a second copy of the content.

Which routes replay

Idempotency is a POST-only mechanism. Sending the header on a GET, PUT, PATCH or DELETE has no effect.

Which routes deliberately do not

Show-once secrets are redacted on replay

Two routes return a credential exactly once. Persisting it in the replay store would undo the reason those credentials are hashed or encrypted at rest, so the stored copy carries a marker instead of the real value. A replay still proves the resource was created exactly once, which is what you retried to find out. The plaintext was only ever on the original response, and this is why you store it before closing the connection.

What counts as the same request

The stored identity is derived from four things:
  1. the Idempotency-Key you sent,
  2. your API key,
  3. the route,
  4. the effective subject.
Two consequences follow. Two different callers can use the same key string without colliding. And the same key under two different X-Exo-Subject values is two distinct identities, so one subject’s response can never replay to another. Alongside that identity, Exo fingerprints the request body. A key is bound to the body it was first used with.

The three outcomes

The mismatch check runs before the in-flight check, so reusing a key for different content fails the same way whether or not the first request has finished.

Failures release the key

A claimed key is always released when the request did not produce a stored response. Any failure after the claim frees it: a 429, a validation failure, a problem response, or a crash. Responses that failed with 400 or 422 are never persisted for replay. That means a corrected retry can reuse the same key. Fix the body, send it again with the same Idempotency-Key, and it will be treated as a fresh attempt rather than refused as a mismatch. If the idempotency store itself is unreachable, the request fails closed with 503 service_degraded rather than proceeding without the claim. Proceeding could double-apply a side effect, which is exactly what the key exists to prevent. Retry with the same key.

Choosing keys

  • One key per logical operation. A UUID is the usual choice.
  • Reuse it for every retry of that operation, including after a timeout where you never saw a response.
  • Never reuse it for different content. That is what idempotency_key_reuse catches.
  • Keys expire after 24 hours. A retry a day later is a new request.

Next

Rate limits

Honouring Retry-After, and why the same key belongs on the retry.

Ingesting content

Where idempotency matters most, and how to read the job result.