Idempotency
Open in the interactive reference
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
{
"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
{
"error": {
"code": "IDEMPOTENCY_KEY_CONFLICT",
"message": "Idempotency-Key 3b2e1a44-9c1f-4f7e-8a6d-2f1c9b7e4a41 is already being processed",
"originalCreatedAt": "2026-01-15T09:30:00.000Z"
}
}