A timeout is not a definite failure
Imagine an export job. The server accepts and stores it, then starts work. Before the response reaches the client, the connection breaks. The client sees a network error while the export already exists. Conversely, the request may never have reached the server. The same local error signal can therefore reflect different states.
For important actions, store a job identifier agreed by client or server. It lets you find the status later. Display an uncertain outcome as ‘being investigated’ if that is accurate; do not mark it ‘failed’ solely because time has passed. A persistently unresolved state needs an accountable resolution path.
This also affects error responses. Stripe explicitly describes 500 responses for certain writes as potentially having an uncertain outcome. A new idempotency key may then trigger a second operation. This is a concrete API-contract example, not a general claim that every 500 response has already had an effect. Stripe: Advanced error handling.
What HTTP means by idempotent
An operation is idempotent when multiple identical calls have the same intended effect as one. Identical response bytes are unnecessary. Repeated DELETE may first report successful removal and later an absent object, for example. Additional log entries do not contradict the HTTP definition either.
HTTP defines PUT, DELETE and safe methods as idempotent, among others. This assumes the application follows method semantics. A GET that secretly triggers a purchasable order does not become harmless through its name. For non-idempotent requests, a client must not infer automatic retry permission solely from a broken connection; it needs additional knowledge of the operation or certainty that it was not applied. RFC 9110, section 9.2.2.
Idempotency is not authorisation either. Every request must be permitted in the current security context. A stored response must not reach someone who happens to use the same key.
An idempotency key needs a server contract
An idempotency key identifies the same logical job across multiple delivery attempts. The client creates it before the first attempt and retains it with the unchanged parameters. The server must actually implement recognition. An arbitrary extra header alone does not change an API's effect.
Agree at least these points:
On narrow screens, scroll the table sideways.
| Contract question | Useful definition |
|---|---|
| Where is the key unique? | For example, within tenant, operation and key; identity comes from verified authentication. |
| What counts as the same job? | A stably defined comparison of relevant parameters; changed content is rejected. |
| What happens with two concurrent attempts? | Atomic acceptance lets at most one execution path begin. The second receives a documented intermediate state. |
| What is stored? | Processing state and an access-protected result reference or the agreed response. |
| How long does recognition apply? | A published window matching the maximum retry and resolution period. |
| How is an uncertain state resolved? | Status query, reconciliation with the executing service or controlled manual handling. |
The following flow diagram is a contract design, not ready-to-use server code:
Schematic explanation · no measurement
Several attempts, the same intended operation
- Client: store the key before the first attempt
The key and unchanged parameters belong to the same logical operation.
- Server: check identity, permission and input
Record the relevant parameters. Then atomically reserve the tenant, operation and key tuple
(Mandant, Operation, Schlüssel)or recognise the existing record.
The atomic decision branches according to the stored state
- New record
- Begin the defined execution path; durably record the final state.
- Same key, different parameters
- Reject: the key must not identify a different operation.
- Same parameters, still pending
- Return the documented intermediate state; do not begin another execution path.
- Same parameters, completed
- Return the result this caller is permitted to receive.
Recovery after a missing response
A timeout does not prove failure. The operation may already be taking effect.
Query the status or retry with the same key and parameters as specified by the contract.
Condition: recognition applies within the agreed time window; every request needs current authorisation. After expiry, establish the outcome first. A new key could trigger a second operation.
A local database lock does not make an external effect atomic. The other service needs a suitable idempotency contract or a traceable reconciliation process; this diagram does not guarantee “exactly once”.
Ordinary ‘look up, then insert’ is insufficient under concurrency. The storage used must reliably enforce uniqueness and state changes. An external effect adds a gap between local storage and the external system. A local database lock does not make sending to a partner atomic. Use its supported idempotency contract or traceable reconciliation; durable job and event mechanisms for complex flows require separate design.
On 27 September 2026, the IETF text on the Idempotency-Key header was expired and archived draft -07. It provides useful contract questions but is no adopted RFC. Its suggestions on header format and error codes are not universal requirements for existing APIs. IETF: draft and status.
Stripe illustrates an existing implementation: repeated requests within its contract can receive the same stored 500 response; keys may be removed after the documented retention window. Do not transfer its duration or error behaviour to other providers without checking. Stripe: Idempotent requests.
When to retry and when to investigate
On narrow screens, scroll the table sideways.
| Situation | Next step |
|---|---|
| Input is invalid or access denied. | Correct the cause; do not blindly repeat the same unchanged error. |
| 429 with Retry-After and a contract permitting retries. | Respect the wait instruction, check the total budget and retain the agreed key for the same action. |
| Connection error after a write call | Treat the outcome as uncertain; use status or the idempotency contract. |
| A job is still being processed. | Use the documented status path; do not create more jobs with new keys. |
| The permitted retry period has expired. | Establish the outcome before starting what may be a new job. |
Retry-After can contain seconds or an HTTP date. It promises no later success and does not automatically make a non-idempotent operation safe. Planning rate limits correctly explains 429 and the operator's side. RFC 9110, section 10.2.3.
Define a shared time and retry budget
Define a total duration within which completing the job makes sense. Include connection setup, response time, waits and every attempt. A library, gateway and your application code should not each retry several times independently: their attempts can multiply and worsen an outage.
Increasing intervals help with temporary errors. A small random variation, commonly called jitter, prevents many clients restarting in step. Respect the server's wait instruction. If it no longer fits your total budget, stop automatic attempts or defer the job under the agreed procedure. ‘Cancelled’ on the client still says nothing about whether an earlier execution already took effect.
The retry policy must suit the service: an interactive page request and a durably stored background job have different time goals. Microsoft describes this trade-off and warns against aggressive and nested retries. Microsoft: Retry pattern.
Test the contract without real side effects
Use an isolated service with synthetic jobs and a simulated partner. Deliberately lose the test response after durable acceptance. A retry must find the same business job; the number of triggered effects must not increase. Also test changed parameters with the same key, concurrent delivery, separate tenants and the end of retention.
Evidence is especially important for uncertain outcomes: interrupt the test flow between the external effect and local result storage. The job must then neither silently disappear nor restart unchecked as a new action. A successful normal case does not prove this property.
Change key structure or retention only with already open jobs in mind. Their entries must remain readable and recognition effective during rollback. Do not delete the idempotency store as a supposedly simple fix. With conflicts, briefly pausing new writes and resolving existing jobs may be safer.
This article's flows have not been tested against a production API. The public API Security Check tests neither duplicate effects nor idempotency stores or client retries. Suitable evidence comes from your application and its integration tests. For multiple distinct, individually valid jobs, continue with securing business logic; for results delivered later, see the webhook guide.
Sources and date
Sources checked: 27 September 2026. Links also appear alongside the statements they support.