# Idempotency

[Open in the interactive reference](https://docs.api.lensing.ai/?op=guide%3Aidempotency)

In practice: generate a UUID for each write you mean to make, send it in the `Idempotency-Key` header, and after a timeout, a `409` or a `500`, send the same request with the same key again. You get the stored response instead of a second write.

Almost every write endpoint accepts the header, and lists it on the Reference tab. Keys work only on requests made with an access token. On a write that does not accept the header, or on a request made without an access token, the header is refused with `400` rather than ignored.

### Choosing a key

A key is any string of 1 to 255 characters. Generate one per logical operation, such as each invoice you mean to create, rather than one per attempt. Store it before the first attempt, so that a retry after a crash can reuse it.

A key belongs to the access token that sent it, across every endpoint and every version: the same key sent to another endpoint is the same key. Renewing a token creates a new token, so keys sent before the renewal are not replayed after it. Finish pending retries before you renew.

### What comes back

| Status | Code | Meaning, and what to do |
| --- | --- | --- |
| `200` | — | The write ran. Keep the key for as long as you may retry. |
| `200` | `Idempotency-Replayed: true` | Nothing ran: this is the stored response to an earlier successful request with this key. Handle it as you handled the first. A replay carries no `diagnostics`. |
| `409` | `IDEMPOTENCY_KEY_CONFLICT` | An earlier request with this key is still running. Wait, then retry with the *same* key. `error.originalCreatedAt` says when that request started; it is absent when both requests arrived at the same moment. |
| `422` | `IDEMPOTENCY_PAYLOAD_MISMATCH` | The key was already used for a different request: another body, endpoint or path. Do not retry; this is a bug in the caller. Use a new key for a new request. The comparison is made against a request that has completed, so while the first one is still running a different body is answered `409` instead, and this answer comes on the retry. |
| `400` | `IDEMPOTENCY_NOT_SUPPORTED` | The endpoint does not accept the header, or the request was not made with an access token. Send the request without the header. |
| `400` | `INVALID_IDEMPOTENCY_KEY` | The header is empty or longer than 255 characters. Checked on every request, reads included, once the `Api-Version` header has been accepted. |

### How replay works

Only a successful request is stored. When a request fails, on a business rule or with an error on the server, the key is released, and a retry runs the write again.

Two requests are the same when the endpoint receives the same input: the JSON body, the path parameters, and the query parameters the endpoint declares. The order of object keys is ignored. Headers are not compared. A request refused as invalid input does not use up its key.

A stored response can be replayed for 24 hours after the write completed. After that the key is free again, and sending it runs a new write.

A request that has not finished five minutes after it started no longer holds its key. A retry after that runs the write again, even if the first request is still running.

Read endpoints, including searches sent as `POST`, do not use the key, but they still refuse an invalid one. Only a replayed response carries `Idempotency-Replayed: true`; any other response omits the header.

## Examples

**First attempt**

```
curl -X POST 'https://<your-tenant-host>/public/api/lensing/accounting/customers' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Api-Version: v1' \
  -H 'Idempotency-Key: 3b2e1a44-9c1f-4f7e-8a6d-2f1c9b7e4a41' \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Acme Inc.", "entityId": "3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45" }'

HTTP/1.1 200 OK

{ "data": { "id": "f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62" } }
```

**Same key, same request, sent again**

```
HTTP/1.1 200 OK
Idempotency-Replayed: true

{ "data": { "id": "f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62" } }
```

**Same key, different request**

```json
{
  "error": {
    "code": "IDEMPOTENCY_PAYLOAD_MISMATCH",
    "message": "Idempotency-Key 3b2e1a44-9c1f-4f7e-8a6d-2f1c9b7e4a41 was already used with a different request payload"
  }
}
```

**Same key, first attempt still running**

```json
{
  "error": {
    "code": "IDEMPOTENCY_KEY_CONFLICT",
    "message": "Idempotency-Key 3b2e1a44-9c1f-4f7e-8a6d-2f1c9b7e4a41 is already being processed",
    "originalCreatedAt": "2026-01-15T09:30:00.000Z"
  }
}
```
