> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useduro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Make any mutation safe to retry — exactly once, even across timeouts.

Network calls fail in the worst way: you send a `POST`, the connection drops, and you don't know whether it landed. Idempotency keys make the retry safe.

## How to use it

Send an `Idempotency-Key` header — any unique string you generate (a UUID is ideal) — on a mutating request:

```bash theme={null}
curl https://api.useduro.com/v1/subscriptions \
  -H "Authorization: Bearer $DURO_KEY" \
  -H "Idempotency-Key: 7f9c2b1e-4a3d-4c6f-9e2a-1b5c6d7e8f90" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": "cus_•••", "planId": "plan_•••" }'
```

If you send the **same key** again, Duro replays the original response — same status, same body — instead of creating a second subscription.

## How it works

```mermaid theme={null}
flowchart TD
    R["POST + Idempotency-Key: K"] --> Q{"(tenantId, K)<br/>recorded?"}
    Q -->|"yes"| REPLAY["return stored<br/>status + body"]
    Q -->|"no"| RUN["run the handler once"]
    RUN --> STORE["store (tenantId, K) →<br/>response"]
    STORE --> RESP["return"]
```

* Keys are scoped **per tenant** (`@@unique([tenantId, key])`) — your keys can't collide with another merchant's, and they can't be probed across tenants.
* Reuse a key only for the *same* logical operation. Reusing it for a different request is a client bug; the stored response won't match what you expect.
* Use a fresh key per distinct intent — typically a UUID per user action.

## When you don't need it

`GET`s are naturally idempotent — no key needed. Some operations are idempotent by design regardless: paying a completed checkout session returns the completed result without re-charging, and creating a renewal invoice is guarded by a per-period unique key in the database. But for your own `POST`s, an `Idempotency-Key` is the simplest way to make retries safe.
