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

# Idempotency

> Safe retries on billable POSTs. One key per logical request, 24-hour TTL.

Set the `Idempotency-Key` header on any retried POST so a network
hiccup never double-charges the team.

```bash theme={null}
curl -X POST https://app.autousers.ai/api/v1/evaluations \
  -H "Authorization: Bearer $AUTOUSERS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "name": "...", "type": "SSE", "...": "..." }'
```

If the request reaches us, we record the response. If you retry with the
**same key** within 24 hours, we replay the original response — same
status, same body, same `X-Request-Id` — without re-running the work.

<Warning>
  **Beta status (2026-05-04):** the `Idempotency-Key` header is accepted but
  enforcement is rolling out. During beta the server records keys but does not
  yet replay; behaviour is "best-effort safe retries". Watch the
  [Changelog](/changelog).
</Warning>

## When to use it

Any time **the request mutates state and a retry could double-bill**. In
practice that is:

| Route                                     | Why idempotency matters                          |
| ----------------------------------------- | ------------------------------------------------ |
| `POST /v1/evaluations`                    | Creates an evaluation. Re-runs spawn duplicates. |
| `POST /v1/evaluations/{id}/ratings`       | Submits a rating. Re-submits skew agreement.     |
| `POST /v1/evaluations/{id}/run-autousers` | Queues paid autouser runs. The expensive one.    |
| `POST /v1/api-keys`                       | Mints a key. Re-mints leak credentials.          |
| `POST /v1/autousers`                      | Creates an autouser persona.                     |
| `POST /v1/templates`                      | Creates a template / dimension.                  |

`GET`, `PATCH`, and `DELETE` are already idempotent at the protocol
level — `Idempotency-Key` is ignored.

## Key format

* Up to 255 characters.
* We recommend a UUID v4 generated by your client per **logical
  request**, not per HTTP attempt. A retry uses the same key.
* Scope: per team. Two teams using the same key string see independent
  records.

```ts theme={null}
import { randomUUID } from "node:crypto";

async function createEvaluationOnce(payload: unknown) {
  const idempotencyKey = randomUUID();
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      return await fetch("https://app.autousers.ai/api/v1/evaluations", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.AUTOUSERS_API_KEY}`,
          "Content-Type": "application/json",
          "Idempotency-Key": idempotencyKey,
        },
        body: JSON.stringify(payload),
      });
    } catch (err) {
      if (attempt === 2) throw err;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
    }
  }
}
```

## Mismatched bodies

If you replay a key with a **different body**, we 409:

```json theme={null}
{
  "error": {
    "message": "Idempotency-Key was previously used with a different request body.",
    "type": "invalid_request_error",
    "code": "idempotency_key_reused",
    "request_id": "0192c7f8-..."
  }
}
```

The check is a sha256 of the canonical request body. The cure is a new
key — pick one fresh UUID per logical request.

## TTL

Records are kept **24 hours** from first use, then garbage-collected.
After that the key is reusable. In practice, generate fresh keys; reuse
is for retries within a single workflow.

## Combining with retries

`Idempotency-Key` and exponential backoff are complementary. The first
makes the server safe to retry; the second decides when.

```ts theme={null}
import { randomUUID } from "node:crypto";

async function postIdempotent(path: string, body: unknown) {
  const key = randomUUID();
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(`https://app.autousers.ai${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.AUTOUSERS_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": key,
      },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    if (res.status >= 500 || res.status === 429) {
      await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
      continue;
    }
    throw new Error(`HTTP ${res.status}`);
  }
}
```

## Webhooks: idempotency on the receiver

Your webhook receiver should be idempotent too. Use the
`Autousers-Event-Id` header as the dedup key. See
[Retry & replay](/webhooks/retry-and-replay).
