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: trueheader — 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
}