Idempotency — retrying safely
Networks fail. A request can reach IDM, succeed, and the answer can still be lost on the way back. The Idempotency-Key header lets you retry without fear: the same intent is carried out once, however many times you send it.
The rule
Create one key per user intent, at the moment the intent happens (the customer taps Book), and send that same key on every retry of that intent until you get a definitive answer. A new intent gets a new key.
Any unique string up to the allowed length works. A UUID is a good choice. Many teams build it from their own order ID, such as book-order-1042.
What IDM does with the key
| Situation | What you get |
|---|---|
| First time IDM sees the key | The request runs normally. |
| Same key, same request, still running | IDM waits up to 2 seconds for the first one to finish and returns its result. If it is still running, 409 REQUEST_IN_FLIGHT with Retry-After: 1. Nothing runs twice. |
| Same key, same request, already finished | The original status code and body are replayed exactly, with the header Idempotency-Replayed: true. No new events, notifications or charges. |
| Same key, different request | 409 IDEMPOTENCY_KEY_REUSED. Nothing runs. You reused a key for a new intent; make a new one. |
| Missing key on an endpoint that requires one | 400 IDEMPOTENCY_KEY_REQUIRED. |
| The first attempt failed with a 5xx | Not recorded, so your retry runs. |
"Same request" means the same method, request URI and JSON body (key order and whitespace do not matter). Keys are remembered for a retention window set by Intellisol (a day at launch), and are scoped to your tenant and the operation.
Which endpoints need a key
Every operation in the API reference shows its rule:
- Required: confirming a quote into a job, Bulk deliveries, webhook registration and replay, cancel, reassign and complete.
- Optional: quotes. With a key, a retry returns the same quote instead of a new one.
- None: reads.
A safe retry loop
async function withRetry(send, attempts = 4) {
for (let i = 0; i < attempts; i++) {
let res;
try {
res = await send(); // signs a fresh timestamp and nonce, reuses the idempotency key
} catch {
await sleep(2 ** i * 500); // network error: retry with the same key
continue;
}
if (res.status === 409) {
const err = await res.json();
if (err.code === 'REQUEST_IN_FLIGHT') { await sleep(1000); continue; }
return res; // a real conflict: read the resource and decide
}
if (res.status >= 500 || res.status === 429) {
await sleep(Number(res.headers.get('retry-after') ?? 2 ** i) * 1000);
continue;
}
return res;
}
throw new Error('IDM did not answer; the same key is safe to retry later');
}
Each retry needs a new nonce and timestamp for its signature, but the same idempotency key.
Business rules that also prevent duplicates
The key is not the only protection. Even after a key has been forgotten:
- A quote can be confirmed into one job only. Confirming it again returns that same job.
- Your
external_order_referenceis unique among your live jobs. Reusing one isEXTERNAL_REFERENCE_IN_USE. - Cancelling a cancelled delivery returns it unchanged.