Idempotency-Key and retrying is safe.
What each case returns
422 means you have a bug. The same key with a different body almost always
means a key was reused across two genuinely different operations — and quietly
running the second one is how duplicates happen.
409 is normal under concurrency and is not an error in your data. Two identical
requests raced; one won. Retry in a moment and you will get the winner’s response.
Choosing a key
- Make it unique per operation, not per retry. All retries of one logical operation share a key; two different orders never do.
- Something you already have works best: an order id, an invoice number, a UUID generated when the user pressed the button.
- 8–255 characters, from
A–Z a–z 0–9 _ . : @ + -. No spaces or control characters — a key differing only by an invisible character is impossible to debug.
Idempotency-Key: 1
cannot collide with another customer’s, and the same key on /v1/leads and
/v1/activities describes two separate operations.
Stored responses expire after 24 hours. After that the same key is a fresh
operation and will run again.
Which endpoints support it
Today:POST /v1/leads and POST /v1/activities.
The header is honoured when supplied but not yet required on these, because the
API is already live and demanding it would reject every existing integration
overnight. New side-effecting endpoints are expected to require it.
We recommend sending it on every POST. It costs nothing when there is no retry,
and it is the only thing that makes a retry safe.
What is stored
Only the response body, its status, and a SHA-256 hash of your request body. The request payload itself is never stored — the hash exists purely to detect a key reused with a different body, and cannot be turned back into your data.If it cannot be checked
If the idempotency store is unreachable, the request is refused with503 SERVICE_UNAVAILABLE, not executed.
This is deliberate, and it is the opposite of how rate limiting behaves (see
rate-limits.md, which fails open). The difference is what
failure costs: a rate limiter that cannot check lets a request through and nothing
is damaged. An idempotency store that cannot check would let a retry through as a
new operation — the duplicate record, or duplicate spend, this whole mechanism
exists to prevent. Refusing is recoverable; a double charge is not.