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

# Idempotency

> Use an idempotency key to make Dairo creates safe to retry, so a repeated request returns the original result instead of a duplicate.

Networks drop, requests time out, and agents re-run steps, so the same `POST` sometimes
fires twice. An idempotency key makes that safe: a retry with the same key returns the
original result instead of creating a duplicate.

## Send an idempotency key

Pass an `Idempotency-Key` header on any create, with a unique value per logical operation.
A UUID is a good default.

```bash theme={null}
curl -X POST https://api.dairo.app/v1/messages \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Idempotency-Key: 2f1c7e9a-8b40-4d2e-9c6a-1f0e5b3a7d21" \
  -H "Content-Type: application/json" \
  -d '{"inboxId":"inbox_123","to":"dev@example.com","subject":"Hi","text":"Hello"}'
```

Retrying that exact request with the same key returns the original message instead of
sending a second one.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  // The key is a per-call option on every create.
  await dairo.messages.send(
    { inboxId: "inbox_123", to: "dev@example.com", subject: "Hi", text: "Hello" },
    { idempotencyKey: "2f1c7e9a-8b40-4d2e-9c6a-1f0e5b3a7d21" },
  );
  ```

  ```python title="Python" theme={null}
  dairo.messages.send(
      inbox_id="inbox_123",
      to="dev@example.com",
      subject="Hi",
      text="Hello",
      idempotency_key="2f1c7e9a-8b40-4d2e-9c6a-1f0e5b3a7d21",
  )
  ```
</CodeGroup>

The key can be up to 128 characters; a longer value is rejected with `400`. A blank or
whitespace-only header is treated as absent.

## Header and body agree

Some creates also accept an `idempotencyKey` in the JSON body. Dairo reconciles the header
and the body field before running the request:

| You send                         | Result                                                          |
| -------------------------------- | --------------------------------------------------------------- |
| Header only                      | The value is used.                                              |
| Body field only                  | The value is used.                                              |
| Both, equal                      | Accepted.                                                       |
| Both, different non-empty values | `400` with code [`idempotency_key_mismatch`](/concepts/errors). |

Set the key either way. A disagreement is always surfaced as an error, never resolved
silently.

## Where it applies

Dairo reconciles the `Idempotency-Key` header into the body on every request, and honors
it on creates plus the action POSTs where a duplicate would be costly:

| Endpoint                                   | What a retry de-duplicates                          |
| ------------------------------------------ | --------------------------------------------------- |
| `POST /v1/messages`                        | sending (or scheduling) the same message twice      |
| `POST /v1/letters`                         | printing and posting the same physical letter twice |
| `POST /v1/inboxes`                         | creating the same inbox twice                       |
| `POST /v1/domains`                         | adding the same domain twice                        |
| `POST /v1/webhooks`                        | registering the same webhook twice                  |
| `POST /v1/api-keys`                        | minting two keys for one request                    |
| `POST /v1/templates`                       | creating the same template twice                    |
| `POST /v1/audiences`                       | creating the same audience twice                    |
| `POST /v1/audiences/{id}/members`          | importing the same members twice                    |
| `POST /v1/audiences/{id}/send`             | broadcasting to an audience twice                   |
| `POST /v1/agents`                          | registering the same agent twice                    |
| `POST /v1/erasure-jobs`                    | enqueuing the same erasure job twice                |
| `POST /v1/inboxes/{id}/verification-waits` | registering the same wait twice                     |

<Note>
  `GET` requests are always idempotent and need no key. `DELETE` and `PATCH` are naturally
  idempotent (deleting or setting the same state twice has the same effect), so they do not
  take an `Idempotency-Key`.
</Note>

## Reuse the key, keep the payload

A key identifies one specific operation, not a slot you can overwrite. On a message send,
reusing a key with a different `subject` or `to` returns the original send unchanged, plus
a warning (`reason: "idempotency_key_reused_with_different_params"`); your new content is
not sent. To send genuinely different content, use a fresh key.

Two habits keep this predictable:

* **Generate the key before the first attempt** and reuse it across every retry of the
  same operation. A fresh key per attempt defeats the purpose, since each new key is a new
  operation.
* **Use a stable, unique value.** A UUID is the simplest safe choice. If you key off your
  own data, make the value unique per intended operation, for example
  `order-4815-welcome-email`.

The SDKs retry transient failures for you; passing an idempotency key makes those retries
duplicate-safe end to end.
