Octri

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 offdefault: on

Off sends no key at all.

idempotency.enabled

Header nametextdefault: Idempotency-Key

The header the key travels in, up to 128 characters. The default is the Stripe convention, which most developers already recognize.

idempotency.headerName

Methodslistdefault: POST, PATCH

Which 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.

yaml
paths:
  /payments:
    post:
      operationId: createPayment
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
Failure on a POST or PATCHHeader not declaredHeader declared
Timeout or network errorNot retriedRetried with the same key
429 or 503RetriedRetried
Any other status, such as 500Not retriedNot retried
Only declare it if your server honors it

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.

  1. Read the key

    Take the header off the request. No key means process normally.

  2. Claim it atomically

    Insert it with a unique constraint before doing the work. A conflict means this is a repeat.

  3. 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.

  4. Handle a repeat of work still running

    If the first request hasn't finished, return 409 rather than starting again.

  5. 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.

typescript
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:

typescript
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.