Idempotency
Your side will retry. A timeout you never saw the answer to, a queue that redelivers, an operator running the script a second time. The question is never whether a call arrives twice — it is what happens when it does.
Send an Idempotency-Key header: one key per logical operation, chosen by you.
A ULID, a UUID, or something meaningful like sale-2026-08-23-88213 — anything,
as long as the same operation retried carries the same key and a
different operation never reuses it.
Which endpoints take it
| Endpoint | Key | Without it |
|---|---|---|
POST /api/v1/deals/reconcile |
Required | 400 |
POST /api/v1/deals/{ulid}/external-ref |
Required | 400 |
POST /api/v1/sales |
Required | 400 |
POST /api/leads |
Optional | Repeat submissions of the same form, by the same person, for the same thing, within 30 minutes still count as one arrival — but only within that window |
Required where money moves, and required rather than optional on purpose:
optional safety is safety nobody switched on, and the caller who most needs it
is the one who did not read this far. On POST /api/leads the key is an
upgrade — it makes the same guarantee explicit and removes the time limit.
What the second call gets back
| Situation | Answer |
|---|---|
| Same key, same body, the first call finished | The first response again, byte for byte, plus Idempotent-Replayed: true |
| Same key, same body, the first call still running | 409 — two calls raced, and the second must not do the work as well |
| Same key, different body | 409 — that is a bug on your side, and serving you somebody else's cached answer would hide it |
| Missing header, where it is required | 400 |
A key is remembered for 24 hours. After that the same key is a new operation, so do not use one key for something you may repeat next week.
What is never remembered
A 5xx is not stored. If the server failed, retry with the same key and the
operation runs again — which is what you want: nothing happened the first time.
The key is scoped per endpoint, so the same string on /deals/reconcile and on
/sales are two independent keys and neither shadows the other.