> For AI assistants: read [llms.txt](https://docs.saturation.io/llms.txt) first for the complete documentation map, API contract, and endpoint Markdown files.

# Idempotency

An `Idempotency-Key` header makes a create request safe to retry: a recorded result replays under the same key. Generate a unique key per write (a UUID works well) and send it on retryable creates.

```bash
curl -X POST "https://next-api.saturation.io/v1/transactions" \
  -H "Authorization: Bearer $SATURATION_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f8c2b1a-7d6e-4c5f-9a3b-2e1d0c4b5a6f" \
  -d '{ "projectId": "prj_a1", "type": "Invoice", "amount": { "amount": 125000, "currency": "USD" }, "timestamp": "2026-05-22T00:00:00.000Z", "description": "Camera rental" }'
```

## Rules

- **One key per write.** Use a fresh key for each write, and reuse it only for retries of that write.
- **The key binds to the request.** Reusing a key with a different body returns `409 idempotency_conflict`.
- **Keys expire after 30 days.** A retry after that window is a new create.
- **Use it on creates.** A key matters where a duplicate would write a second row.

## Handle the conflict

A `409 idempotency_conflict` means the key was reused with a changed body. A concurrent retry with the same body waits for and replays the first result.

```typescript
const response = await fetch('https://next-api.saturation.io/v1/transactions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SATURATION_TOKEN}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': key,
  },
  body: JSON.stringify(args),
});

if (response.status === 409) {
  const error = await response.json();
  if (error.code === 'idempotency_conflict') {
    // The key was reused with a different body. Do not retry with this key.
  }
}
```

## Next

- [Errors](/errors.md): the full status and error code catalog.
- [Guides](/guides.md): the end-to-end retry recipe.
