Idempotency
An idempotency key makes a retry safe. Send the same Idempotency-Key with the
same request and it runs once, however many times you send it. The repeat
returns the original response with Idempotency-Replayed: true.
Every POST, PATCH and DELETE accepts the header, except the routes
that ignore it. An API key must send it on the
writes that move money or create something durable, or the call is refused with
400 IDEMPOTENCY_KEY_REQUIRED:
| Required on | Route |
|---|---|
| Recipients | POST /, POST / |
| Payouts | POST /, …/quotes/accept, …/ |
| Batches | POST …/, …/, …/ |
| Links and funding | POST …/payout-links, …/, …/sandbox/fund |
| Checkout | Under /: POST/PATCH/DELETE on products and links, …/, …/, …/, and the sandbox …/simulate routes |
On every other write, such as correcting a recipient, pricing a payout or pausing a webhook endpoint, the header is optional; without it nothing is stored for replay. Dashboard sessions don’t have to send one, except on the checkout refund route, which requires it from every caller.
A key is 1–255 characters from [A-Za-z0-9_.:-]. Use a UUID v4, one per
logical operation (one payout, one recipient), and persist it before you send.
A retry repeats the method, path, body and key. Keys are scoped to your
organization, not to the API key that sent them: the same key sent by another
of your API keys (in the same mode) replays or conflicts with the first
request. A completed record is
kept for seven days, then deleted, and the same key after that runs again.
POST /payments/organizations/cmsx…/payoutsx-api-key: avvio_live_…Idempotency-Key: 8f14e45f-ea0f-4b1a-9b0e-2c1d3e4f5a6bContent-Type: application/json
{"amount":"200.00","destinationAccountId":"sbx_acct_MXN_4471_ae66cbc5"}Money-moving routes also refuse an identical body under a different key for 15
minutes with 409 DUPLICATE_REQUEST_DETECTED. They
are POST …/quotes/accept, …/payouts, …/payouts/batches,
…/payouts/{payoutId}/funding/confirm and the checkout
…/payments/{paymentId}/refund. On each of them, X-Allow-Duplicate: true
turns that guard off. It does not replace
Idempotency-Key.
Routes that ignore the header
Section titled “Routes that ignore the header”POST /payout-links/{token}/submit. The single-use link token is already the idempotency, and there is no caller identity to scope a key to.- Routes that return a secret once: creating and rotating an API key, and creating a webhook endpoint (including the sandbox one) or rotating its secret. A stored replay would keep the secret for seven days, so if you lose the response, rotate again.
- Requests whose body is not JSON, such as multipart uploads.
Responses
Section titled “Responses”| Response | Type or header | Meaning | What to do |
|---|---|---|---|
400 |
IDEMPOTENCY_ |
The header is missing on a route that requires it | Add it and retry |
400 |
IDEMPOTENCY_ |
Not 1–255 characters of [A-Za-z0-9_.:-] |
Use a UUID v4 |
409 |
IDEMPOTENCY_ |
The key was used with a different body | Don’t retry. A different request needs a new key |
409 |
IDEMPOTENCY_ |
An identical request is still running | Back off, retry with the same key |
500 |
PAYOUT_ |
The network didn’t answer after the send left. The payout may exist and the key is kept | Keep the key. Do not retry with a new key. Poll GET / or replay, and send support the requestId |
409 |
PAYOUT_ |
A replay of that key. Nothing re-executed | As above, with originalRequestId; still no new key. Once resolved, the replay returns the real receipt |
503 |
IDEMPOTENCY_ |
We couldn’t store the key. Nothing executed | Back off, retry with the same key |
200/201/202 |
Idempotency-Replayed: true |
The original result, with the original code: 201 on creates (/recipients, /methods, /payout-links, checkout products and links), 202 on POST …/ and on a payout held for approval, 200 on the rest |
Treat it as success. Don’t re-send |
Timeouts
Section titled “Timeouts”After a timeout or connection reset, retry the identical request with the same
key, or look it up with GET /payments/organizations/{orgId}/orders?reference=.
Never re-quote and send under a new key.
When our upstream times out you get 500 PAYOUT_OUTCOME_UNKNOWN, and replays
answer 409 PAYOUT_OUTCOME_UNKNOWN until we confirm the outcome with the
network. A plain 500 INTERNAL is different: nothing was recorded, the key was
released, and retrying with it is safe.
Key release versus terminal failures
Section titled “Key release versus terminal failures”- A
4xxvalidation failure releases the key. Fix the payload and reuse it. - A payout the rail refuses at once with a
4xx(PROVIDER_REJECTED) releases the key like any other4xx. A payout that is accepted and then fails later (invalid bank routing, for example) is a terminal attempt: a new attempt needs a new key, and it is a second payment, so send it only once the first showsfundsReturned: true(Track status & failures). - A
PAYOUT_OUTCOME_UNKNOWNkey is held until support resolves it and is exempt from the seven-day sweep, so it never quietly becomes re-executable. - A request that dies mid-flight holds its key in progress for about two
minutes. Then a money key becomes
PAYOUT_OUTCOME_UNKNOWN, and a key that moves no money (creating a recipient) is released so your retry runs.
Was this page helpful?