Idempotency
Networks fail. A request can time out after the server has already acted on it. Send an
Idempotency-Key header on every POST and DELETE, and you can retry safely: the second attempt
returns the first attempt's response instead of creating a second analysis or confirming twice.
POST /v1/meal-analyses HTTP/1.1
Authorization: Bearer opis_live_…
Idempotency-Key: 6f1c2e1a-4f0b-4a7e-9a0e-8d8f7d9a5c21
Content-Type: application/jsonRules
- Key: any string up to 255 characters; a UUID v4 per logical operation is ideal. Generate it once, before the first attempt, and reuse it on every retry of that operation.
- Scope: keys are scoped to your organization. Do not reuse a key across projects.
- Lifetime: keys are remembered for 24 hours.
- Fingerprint: we store a hash of the method, path, body and (for uploads) the file's SHA-256.
| Situation | Response |
|---|---|
| First request | Processed normally; the response is stored |
| Same key, same request, finished | The stored response is replayed — same status and body — with Idempotent-Replayed: true |
| Same key, same request, still running | 409 idempotency_request_in_progress with Retry-After: 1 |
| Same key, different request | 422 idempotency_key_reused |
| After 24 hours | The key is forgotten and treated as new |
Client errors (4xx) are stored and replayed like successes. Server errors (5xx) are not
stored, so retrying with the same key after a 5xx runs the request again.
Retry recipe
async function postWithRetry(url: string, body: unknown, attempts = 4) {
const key = crypto.randomUUID(); // one key for all attempts
for (let i = 0; i < attempts; i++) {
try {
const res = await fetch(url, {
method: 'POST',
headers: {
...auth,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify(body),
});
if (res.status !== 409 && res.status < 500) return res;
const wait = Number(res.headers.get('retry-after') ?? 2 ** i);
await new Promise((r) => setTimeout(r, wait * 1000));
} catch {
await new Promise((r) => setTimeout(r, 2 ** i * 1000)); // network error: retry, same key
}
}
throw new Error('Gave up after retries');
}Where it matters most
- Creating analyses and estimates — a duplicate is billed twice.
- Confirming — combine
Idempotency-KeywithIf-Match(see Confirmation). A replayed confirm returns the original202; without a key, a second confirm returns409 invalid_state. - Uploads — a retried multipart upload with the same key returns the same
file_….