Idempotency
Exactly how the Idempotency-Key behaves: format, scope, 24-hour window, what is compared, what is memorised, and what happens on a retry, a conflict or concurrent requests.
The Idempotency-Key header makes a request safe to retry: if the response is lost on the network, the same request sent again with the same key returns the response already computed instead of computing and billing a second time.
Format
- 1 to 255 bytes, visible ASCII only (
0x21to0x7E): no space, no tab, no accented character. - No normalisation: the value you send is the value stored, compared byte for byte.
- An empty, too long, repeated header, or one containing a forbidden character, is refused with
400 invalid_idempotency_key, before any computation.
One UUID per logical operation is a good choice. Generate it once, before the first attempt, and keep it for every retry of that operation.
Scope and window
| Property | Value |
|---|---|
| Scope | The workspace and environment of the API key. Every API key of a workspace shares this space; Sandbox and Production are separate. |
| Window | 24 hours from the first request. |
| After 24 hours | The key becomes free again, including for a different request. |
What is compared
Two requests are "the same request" when they concern the same operation (creation, or refresh of a given target), the same economic input in its canonical form and the same external_transaction_id. JSON field order, case and whitespace in descriptor, or a fraction of a second in occurred_at change nothing. A different amount, currency or identifier makes a different request.
The same key used for a create call and then for a refresh designates two different requests: the second returns 409 idempotency_key_reuse.
What happens
| Situation | Response |
|---|---|
| First request with this key | Processed normally |
Same key, same request, after a 200 | The memorised body of the first response, Noryo-Idempotency-Replayed: true. Nothing is recomputed or billed. |
| Same key, different request, within 24 hours | 409 idempotency_key_reuse |
| Same key, same request, while the first is still in progress | The second waits for the first to finish, then returns its response as a replay; if the wait lasts too long, 503 busy with Retry-After |
Same key, same request, after a refusal returned at settlement (402, 403 production_not_enabled, 409 transaction_id_conflict, 410, 503) | Re-evaluated as a new attempt: a refusal is not memorised |
| Same key, different request, after such a refusal | 409 idempotency_key_reuse: a refusal does not free the key |
Same key after a refusal that came before settlement (validation 400, 401, 403 insufficient_scope, 429) | The key had not been reserved: it is still free |
| Same key after 24 hours | Processed as a first request |
Two rules sum up the table. Only a success is memorised: an error is replayed by recomputing, which lets you retry after a failure. The link between a key and its request never changes for 24 hours, even after an error: a key is not recycled for another payload.
What a replay contains
- The same body,
usageincluded: a response that was billed originally still showsbillable: true. Do not count it twice. - The
Noryo-Idempotency-Replayed: trueheader. - A new
Noryo-Request-Id: every attempt has its own identifier, replays included. - One rate-limit token consumed, like any authenticated request.
Concurrent requests
Two identical requests sent at the same time with the same key never produce two settlements. The second waits for the first instead of being refused: it then receives the replay of the response. If the wait exceeds the delay the server allows itself, it receives 503 busy with a Retry-After: nothing was written or billed, and the same request can be sent again as it is.
{
"type": "https://developer.noryo.app/errors/busy",
"title": "Service busy",
"status": 503,
"code": "busy",
"detail": "The service is temporarily contended. Retry after the delay given by Retry-After.",
"request_id": "req_…"
}JSONChoosing keys
- One key per logical operation, never one key per session or per customer.
- Several services in one workspace share the key space: prefix them, for example
billing-<uuid>. - A retry reuses the same key; a new operation takes a new one.
- Put no personal data in the key: it is not logged in clear, but nothing justifies placing information in it.
Published on 2026-10-08 21:04 UTC (version 1).