Retries
Networks fail. An Idempotency-Key lets you repeat a write safely: it happens once, however many times you send it.
How it works
- Pick a key for the change you mean. 8–100 characters, unique to that one change, like
order-1042-addor a random UUID you store with the job. - Send it with the write in the
Idempotency-Keyheader. - If you don’t get an answer, send the exact same call again with the same key. If the first one went through, you get its outcome back, marked
Idempotent-Replayed: true, and nothing is written twice.
Response 200, replayed
{
"replayed": true,
"outcome": "done",
"refs": ["tr_…"],
"note": "This request already ran. Read the objects again for their current state."
}Good to know
- The same key with a different body is refused with
conflict: a key stands for one change. - While the first call is still running, a repeat gets
in_progress. Wait a moment and repeat it again. - If a first attempt’s outcome can’t be known after ten minutes, you get
unknown_outcomerather than a second write. Read the objects to see what happened. - Outcomes are kept for seven days. Use a new key for each new change.
A retry loop
// Retry a call on 429 or a server error, keeping the same Idempotency-Key.
async function send(url, options, attempts = 4) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429 && response.status < 500) return response;
if (attempt === attempts) return response;
const wait = Number(response.headers.get("Retry-After")) || 2 ** attempt;
await new Promise(resolve => setTimeout(resolve, wait * 1000));
}
}
await send(`${BASE}/tables/tt_…/records`, {
method: "POST",
headers: { ...headers, "Idempotency-Key": `order-${order.id}-add` },
body: JSON.stringify({ records: [{ cells: { tf_customer: order.customer } }] }),
});Never retry a write without an Idempotency-Key. Without one, a retry after a dropped answer can write twice.