Skip to content

Idempotency

An idempotency key makes a retried write safe: the Verb runs once, however often it is sent.

An Idempotency-Key binds a durable call to exactly one Action. Send the same request twice with the same key, and the Verb runs once.

Why a retry needs a key

glyphs.store is a Startup that sells font licenses. Its checkout Agent places an Order, the card is charged, and the connection drops before the response arrives. The Agent cannot tell whether the Order exists.

Without a key, a retry places a second Order and charges the card twice. With a key, the retry returns the first Action and its Order, and nothing runs again.

Send a key

Pass the key on every durable write, the same way on each Interface:

Terminal
# API
curl -X POST https://api.sb/glyphs.store/orders/place \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Idempotency-Key: cart_8812-checkout" \
  -H "Content-Type: application/json" \
  -d '{"offer":"offer_serif_family"}'

# CLI
npx startup-builder do Order.place --idempotency-key cart_8812-checkout \
  --json '{"offer":"offer_serif_family"}'
TypeScript
// SDK, and the same line inside an MCP `do` script
const action = await $.Order.place(
  { offer: 'offer_serif_family' },
  { idempotencyKey: 'cart_8812-checkout' },
).settled

A key is any string up to 255 characters. Derive it from the business event, such as the cart id, so every process that retries the checkout sends the same key.

What a key binds

A key belongs to one namespace and one calling Worker, so two Agents never collide. It binds the operation and a fingerprint of the input. The second request with that key gets one of four answers:

Second requestResponse
Same input, first Action still pendingThat pending Action; the call waits on it as its Mode says
Same input, first Action settledThat Action in its final status, with Idempotent-Replayed: true
Different operation or input409 idempotency-mismatch; nothing runs
First request rejected at the doorA fresh run: a 400, 401, 403 or 429 recorded nothing

A replay never re-runs a Verb, never emits a second Event and never meters twice.

How long a key holds

A key holds forever. Every Action is kept forever, so a replay a year later still returns the Action the key first named.

Reuse a key only for the same intent. A new purchase by the same Customer is a new business event, so it gets a new key.

Retrying a failure

A replay returns a failed Action as failed; the key does not try again. To run the Verb again, call Action.retry, which starts a new Action linked to the failed one by on. Or send the request with a new key.

The SDK and the CLI generate a key for every durable call and reuse it across their own retries. Pass your own key when a retry can come from a restarted process, a queue redelivery or another machine. try calls store nothing and need no key.

Writes queued offline

An App that queues writes offline sends each one later with its key and the Version of the record it was decided against. On replay, the platform runs it when the record's changes since then touch none of the Verb's inputs.

When they do, the Action is refused with a fix, and the write lands in the caller's inbox to decide again. A price or an Approval never applies to a record its approver did not see.

Next