Idempotency

Networks fail mid-request. Idempotency keys make retrying safe — the same request can never move money twice.

Every money-moving POST (/cards, /cards/{id}/topups, /test/credit) requires an Idempotency-Key header — any string up to 255 characters that uniquely names this operation in your system (an order id, a UUID). Requests without one are refused with 400 idempotency_key_required rather than guessed at.

the golden rule
Timeout or 5xx?   → RETRY with the SAME key. You get the stored outcome, not a second card.
New operation?    → use a NEW key.

What each response means

  • Idempotency-Replayed: true header — this is the stored response from your original request, byte-identical. Nothing moved again.
  • 409 idempotency_in_flight — the original attempt is still processing. Wait a few seconds and retry the same key.
  • 422 idempotency_conflict — same key, different request body. That's a bug on your side: a key names one exact operation, forever.

Example

safe retry loop (pseudocode)
const key = `topup-${order.id}`          // stable per operation
for (let attempt = 0; attempt < 3; attempt++) {
  const res = await fetch(url, { headers: { "Idempotency-Key": key, … } })
  if (res.status < 500) return res         // success OR a definitive error
  await sleep(2 ** attempt * 1000)         // 5xx/timeout: same key, try again
}