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
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.POST /v1/keysreplays withapiKeyreplaced.POST /v1/webhooksreplays withsecretreplaced.
What counts as the same request
The stored identity is derived from four things:- the
Idempotency-Keyyou sent, - your API key,
- the route,
- the effective subject.
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 sameIdempotency-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_reusecatches. - 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.