Idempotency
A write that times out may still have succeeded. Retrying it could create a second charge, a second user or a second order. Generated clients send an idempotency key on writes, so your server can recognize a repeat and return the original result instead of doing the work twice.
Settings
Set these project-wide in SDK Studio, or on one endpoint. Configure nothing and you get the defaults below.
Generate an idempotency keyon or offOff sends no key at all.
idempotency.enabled
Header nametextThe header the key travels in, up to 128 characters. The default is the Stripe convention, which most developers already recognize.
idempotency.headerName
MethodslistWhich methods get a key: any of POST, PUT, PATCH and DELETE.
idempotency.methods, such as ["POST", "PATCH", "DELETE"]
The key is a v4 UUID. It's generated once per call and reused across that call's retries, which is what lets your server spot them as repeats.
An endpoint override merges over your project default field by field, so an endpoint that only turns generation off keeps your custom header name.
Making writes retry safely
Sending a key isn't, on its own, enough for a write to be retried after a lost response. A timeout says nothing about whether the server processed the request, so the client retries a POST or PATCH only when your spec says your server deduplicates.
Declare it with a header parameter on the operation. Any header with idempotency in its name counts.
paths:
/payments:
post:
operationId: createPayment
parameters:
- name: Idempotency-Key
in: header
required: false
schema:
type: stringFailure on a POST or PATCH | Header not declared | Header declared |
|---|---|---|
| Timeout or network error | Not retried | Retried with the same key |
429 or 503 | Retried | Retried |
Any other status, such as 500 | Not retried | Not retried |
Declaring the header tells the client a repeat is safe. If your server doesn't really deduplicate, a retried write will run twice.
Your server has to honor it
The client generating a key is half the contract. Your server has to store the key and, on a repeat, return the original response instead of doing the work again. Without that, the header is an ignored string.
- Read the key
Take the header off the request. No key means process normally.
- Claim it atomically
Insert it with a unique constraint before doing the work. A conflict means this is a repeat.
- Replay the stored response
Store the status and body against the key when the work completes, and return them for repeats. Scope keys per account so one customer can't probe another's.
- Handle a repeat of work still running
If the first request hasn't finished, return
409rather than starting again. - Expire them
Keys only need to outlive a client's retry window. 24 hours is generous.
What developers can do
A developer can set their own key on a call, which is useful for tying it to something in their system, such as an order ID. A per-call key wins in both directions: it's sent even if generation is off.
await client.users.create(
{ email: "ada@acme.com" },
{ idempotencyKey: `signup-${orderId}` },
);
// null sends no key for this call
await client.users.create({ email: "ada@acme.com" }, { idempotencyKey: null });They can also change the policy for their whole client:
const client = new Acme({
auth: { bearer: process.env.API_TOKEN! },
idempotency: { methods: ["POST", "DELETE"] },
});From the CLI
octri sdk settings set idempotency.headerName X-Request-Id changes one value. The keys are in the settings reference, and the command in SDK commands.