Idempotency-Key never executes side effects twice.
How it works
Include anIdempotency-Key header on every request to an endpoint that requires one. Natural records the key alongside the outcome of the request, and a later request with the same key returns that recorded outcome instead of running the operation again.
- Same key + same request + finished -> Replays the recorded response.
- Same key + different request -> Returns
409 conflict. - Same key + original still running -> Returns
409 conflict. - Different key -> Treated as a new mutation attempt.
X-Idempotency-Replayed: true. That header is the only thing distinguishing a replay from a fresh execution, so check it if you need to tell them apart.
Endpoints that require a key
TheIdempotency-Key header is required on the endpoints below. Each one is a mutating operation whose accidental repetition would be visible to you or your customers.
Omitting the header on any of these returns
400 with the error code invalid_value. A key longer than 255 characters is rejected the same way. Endpoints not listed here ignore the header.
Keys
A key must be unique per logical operation and stable across every retry of that operation. Natural binds the key to the meaningful contents of the request it first arrives with, so the same key must always mean the same request.- Use a UUIDv4/UUIDv7, or any unique string of up to 255 characters.
- Generate the key once, when the operation is first attempted, and reuse it for every retry of that attempt, including retries after a timeout.
- Generate a new key only when the user or system starts a genuinely new operation.
- Never derive a key from anything that changes between retries (a timestamp, an attempt counter, a per-request random value). A key that changes per retry provides no protection at all.
- Don’t put sensitive data in keys.
Replay window
A record is created when the request starts, and is finalized once the operation reaches a terminal outcome: success or failure. The record then expires 48 hours after it finished, not 48 hours after the request arrived. Within those 48 hours, the same key replays the recorded outcome. Once they elapse the key is forgotten, and reusing it starts an entirely new request, re-running the side effect. Treat 48 hours as the window in which a retry is guaranteed safe, not as a deduplication guarantee for the life of the key. A record for an operation that is still running does not expire on its own. If an operation never reaches a terminal outcome, its key stays reserved and requests reusing it keep returning409 conflict. Contact support if a key stays conflicted longer than you would expect.
Conflicts
Both conflict cases return409 with the error code conflict:
- You retried an identical request. The original is still running. Wait, then retry the same key with exponential backoff.
- You changed the request but reused the key. This is a client bug: a key is bound to the first request sent under it. Use a fresh key for the new operation.
meta.supportId when contacting support about a conflict; the underlying reason is recorded against that ID.
Failures are replayed too
Failures are recorded exactly like successes. When an operation fails terminally, the same key replays that same error (identical code, status, and detail) for the rest of the window. Retrying a recorded failure with the same key does not re-run it. Two cases release the record rather than recording it, because neither reflects a decision about your request:408 (timeout) and 429 (rate limited). Retrying either with the same key genuinely re-runs the operation.
When to retry
Related
- Error Handling - Handling conflicts and other errors