POST /v1/transactions request that times out at your end might have succeeded on our side. Without idempotency, retrying would create a duplicate. The Idempotency-Key header solves this.
How it works
Send anIdempotency-Key header on any mutating request (POST, PATCH, DELETE). It is required on POST - every create and every RPC-style action endpoint (.../reverse, .../deliver, .../rotate_secret, and so on). It is optional but recommended on PATCH and DELETE: omit it and the request just runs without dedup or replay protection; send it and you get the identical claim/replay/conflict guarantees POST gets. The value is a stable opaque string, 8 to 255 characters.
- First request: we process normally and cache the response keyed by
(api key, method, path, idempotency key) - Retry with same key AND same request (body and query string): we replay the cached response, with no duplicate side effect. Response header
Idempotent-Replayed: true - Retry with same key but a different body or query string: we return
409withtype: "idempotency_error",code: "idempotency_key_conflict"(safety net against bugs). Use a new key to retry safely. - Retry while the first request is still in flight: we return
409withcode: "idempotency_key_in_use", so retry shortly.
2xx or 4xx. If the first attempt ended in a 5xx, or the connection broke before it finished, the retry runs again, and a side effect that already happened (for example a delivery that was sent) can happen a second time.
Attempt 2 never re-runs your write. The API recognizes the key, hands back the exact response it cached from attempt 1, and stamps it
Idempotent-Replayed: true so your app can tell the two apart in logs even though the body is identical.New key
No cached response exists yet. Executes the write, caches the result, returns it fresh.
Same key, same request
Cached response found and the request (body and query string) matches. Replays it.
Idempotent-Replayed: true, no re-execution.Same key, different request
Cached response found but the body or query string doesn’t match.
409 idempotency_key_conflict - use a new key.Same key, still in flight
An earlier request with this key hasn’t finished yet.
409 idempotency_key_in_use - retry shortly.409 cases, see the Errors reference.
Exception:
POST /v1/notifications. The 1 per minute broadcast limiter runs before idempotency on this endpoint. A retry inside that window returns 429 rate_limited_per_minute, not a cached replay. Wait for Retry-After, then retry with the same key.5xx response is not cached: the claim is released, so the same key can be retried and runs fresh. Only 2xx and 4xx responses are replayed.
Which endpoints require it
EveryPOST endpoint requires Idempotency-Key - creates and RPC-style actions alike. Omitting it returns 400 with type: "invalid_request_error", code: "idempotency_key_required".
PATCH and DELETE endpoints accept the header but do not require it: omitting it just means that particular call runs without dedup or replay protection, not an error. We recommend sending it on those too for a fully deterministic retry story, especially for DELETE calls you might retry after a network timeout.
Read-only endpoints (GET) ignore it.
Key format
- 8 to 255 characters
- Any characters allowed, though we recommend URL-safe:
A-Z a-z 0-9 - _ . - Must be unique per logical operation
Best practices
Use a stable value per logical operation, not per retry. If you’re recording a POS purchase, the natural idempotency key is the POS order ID:Example
Verifying replay
TheIdempotent-Replayed: true response header tells you the response came from cache instead of a fresh execution: