Idempotency

Safely retry payout creation without ever sending the same money twice.

Networks fail. A request times out, your process restarts mid-call, a load balancer retries — and you can't tell whether the payout was created. Idempotency makes retrying safe: the same request sent twice creates one payout, not two.

How it works

Send an Idempotency-Key header on POST /v1/payouts. It is required on payout creation. The key is a unique value you generate per logical payout — a UUID is ideal.

bash
curl https://api.caiboglobal.com/v1/payouts \
  -H "X-API-Key: $CAIBO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6b2f1e0a-7c3d-4a91-9f2e-1a2b3c4d5e6f" \
  -d '{ "amount": "25.00", "currency": "USDC", "rail": "stablecoin",
        "destination": { "network": "polygon", "address": "0x1234...5678" } }'

The server keys the payout on (organization, idempotency_key) and behaves like this:

  • First request with a given key → the payout is created and returned.
  • Retry with the same key and the same request body → the original payout is returned. No second payout is created.
  • Reuse of the key with a different body409 Conflict, code idempotency_conflict. No payout is created.

What "same request" means

A retry is considered the same when the amount, currency, and destination match the original. Reusing a key for a genuinely different payout is treated as a mistake and rejected with idempotency_conflict — this protects you from accidentally overwriting one payout's intent with another's.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "idempotency_conflict",
    "message": "Idempotency key was reused with a different request",
    "request_id": "req_2b8f1c9d"
  }
}

Generating keys

Pick a key that is unique to the payout's intent. Two good strategies:

  • A fresh UUID per attempt-set. Generate it once, before the first send, and reuse that same value for every retry of that one payout.
  • A deterministic key from your own data, e.g. payout:invoice_1001. This guarantees that even a completely fresh process retrying the same business action produces the same key.
Generate the key before the first request and persist it with your record of the payout. If you generate a new key on each retry, retries are no longer idempotent.

A safe create-and-retry pattern

javascript
async function createPayout(payout, idempotencyKey) {
  const attempt = () =>
    fetch("https://api.caiboglobal.com/v1/payouts", {
      method: "POST",
      headers: {
        "X-API-Key": process.env.CAIBO_API_KEY,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey, // SAME value on every retry
      },
      body: JSON.stringify(payout),
    });

  for (let i = 0; i < 3; i++) {
    const res = await attempt();
    if (res.ok) return res.json();
    // Retry only on transient errors; a 4xx (e.g. 409) is terminal.
    if (res.status < 500) throw new Error(await res.text());
    await new Promise((r) => setTimeout(r, 500 * 2 ** i));
  }
  throw new Error("payout creation failed after retries");
}

Scope of idempotency

  • Idempotency applies to payout creation, the one endpoint where a duplicate would move money twice.
  • Read endpoints (GET) are naturally idempotent and need no key.
  • Cancel is safe to call more than once: cancelling an already-cancelled payout is a no-op error, never a double effect.