# Idempotency

Networks fail. To make retries safe, every write request (`POST`, `PUT`, `PATCH`, `DELETE`) must carry an
idempotency key:

```http
X-Idempotency-Key: create-job-REQ-1042
```

A write without the header is rejected with `400 VALIDATION_ERROR`. The token endpoints are the exception.

## How keys behave

Keys are scoped to your partner account **and** the tenant in `X-Tenant-ID`, and are kept for **24 hours**.

| You send | Result |
| --- | --- |
| A new key | The request runs normally. |
| The same key and the same body again | The original status and response body are returned; nothing runs twice. |
| The same key with a different body | `409 IDEMPOTENCY_CONFLICT` |
| The same key while the first request is still running | `409 IDEMPOTENCY_CONFLICT` with `retryable: true` — wait and retry. |

Only responses below `500` are stored. If the original request failed with a server error, retrying with the
same key runs it again.

## Choosing keys

- Use one key per logical operation, and reuse it for every retry of that operation.
- Derive it from your own data where you can, e.g. `create-job-<external_job_id>` or
  `decision-<candidate_id>-<your event id>`. A random UUID stored with the pending operation also works.
- Don't reuse a key for a different operation.

If the idempotency store is briefly unavailable the API returns `503 SERVICE_UNAVAILABLE`; retry with the same
key after a short backoff.
