# Idempotency

URL: /code/api/guides/idempotency · Id: `page:code/api/guides/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](/business/nouns/action). Send the same request twice with the same key, and the [Verb](/business/nouns/verb) runs once.

## Why a retry needs a key
`glyphs.store` is a [Startup](/business/nouns/startup) that sells font licenses. Its checkout [Agent](/business/nouns/agent) places an [Order](/business/nouns/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](/business/nouns/interface):

```bash
# 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"}'
```

```ts
// 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](/business/nouns/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 request | Response |
| --- | --- |
| Same input, first Action still `pending` | That `pending` Action; the call waits on it as its [Mode](/business/nouns/mode) says |
| Same input, first Action settled | That Action in its final status, with `Idempotent-Replayed: true` |
| Different operation or input | `409` [idempotency-mismatch](/code/api/errors/idempotency-mismatch); nothing runs |
| First request rejected at the door | A fresh run: a `400`, `401`, `403` or `429` recorded nothing |

A replay never re-runs a Verb, never emits a second [Event](/business/nouns/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](/business/nouns/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`](/business/nouns/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](/business/nouns/app) that queues writes offline sends each one later with its key and the [Version](/business/nouns/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](/business/nouns/approval) never applies to a record its approver did not see.

## Next
- [Actions](/code/sdk/guides/actions): the record a key returns.
- [Throttling](/code/api/guides/throttling): when a retry waits, and for how long.
- [Errors and testing](/code/sdk/guides/errors-and-testing): which errors to retry.