Skip to content

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 /recipients/{orgId}, POST /recipients/{orgId}/{recipientId}/methods
Payouts POST /payments/organizations/{orgId}/payouts, …/quotes/accept, …/payouts/{payoutId}/cancel
Batches POST …/payouts/batches, …/batches/{batchId}/confirm, …/batches/{batchId}/cancel
Links and funding POST …/payout-links, …/payouts/{payoutId}/funding/confirm, …/sandbox/fund
Checkout Under /checkout/organizations/{orgId}: POST/PATCH/DELETE on products and links, …/links/{linkId}/publish, …/links/{linkId}/pause, …/payments/{paymentId}/refund, 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…/payouts
x-api-key: avvio_live_…
Idempotency-Key: 8f14e45f-ea0f-4b1a-9b0e-2c1d3e4f5a6b
Content-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.

  • 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.
Response Type or header Meaning What to do
400 IDEMPOTENCY_KEY_REQUIRED The header is missing on a route that requires it Add it and retry
400 IDEMPOTENCY_KEY_INVALID Not 1–255 characters of [A-Za-z0-9_.:-] Use a UUID v4
409 IDEMPOTENCY_KEY_CONFLICT The key was used with a different body Don’t retry. A different request needs a new key
409 IDEMPOTENCY_KEY_REQUEST_IN_PROGRESS An identical request is still running Back off, retry with the same key
500 PAYOUT_OUTCOME_UNKNOWN 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 /orders?reference= or replay, and send support the requestId
409 PAYOUT_OUTCOME_UNKNOWN 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_UNAVAILABLE 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 …/payouts/batches and on a payout held for approval, 200 on the rest Treat it as success. Don’t re-send

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.

  • A 4xx validation 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 other 4xx. 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 shows fundsReturned: true (Track status & failures).
  • A PAYOUT_OUTCOME_UNKNOWN key 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?