Idempotency-Key. That is the whole reason the header is mandatory.
What to retry
Because client faults are never returned as
5xx on this API, the split above is
reliable: a 5xx really does mean our side, and retrying it is reasonable.
The timeout case
This is the case retries exist for, and the one people get wrong. When a request times out you do not know whether the server acted. The connection died, but the write may well have committed. Without an idempotency key your options are to retry and risk a duplicate order, or not retry and risk losing it. With one, retry. If the original committed, you get its stored response back and nothing happens twice. If it never arrived, it runs now.Backing off
1
Honour Retry-After when it is present
On a
429 the server tells you exactly how long to wait. Use that number rather
than your own schedule.2
Otherwise back off exponentially
Roughly 1s, 2s, 4s, 8s, 16s. A fixed short delay against an overloaded service adds
load at the worst moment.
3
Add jitter
Randomise each delay by up to a few hundred milliseconds. Without it, every client
that failed at the same instant retries at the same instant, and the recovering
service is hit by a synchronised wave.
4
Cap the attempts
Five is usually right. Beyond that you are not recovering from a blip, and the
failure should surface to a human or a dead letter queue.
A worked implementation
Reads
GET requests are naturally safe to retry and take no idempotency key. Apply the same
backoff rules.
If a paged walk fails mid-way, retry the failed page with the same after cursor. You
do not need to restart the walk.
When to stop and alert
Retries hide transient failures, which is their job. They should not hide a persistent one. If an operation exhausts its attempts, surface it: a log at error level with thex-request-id, a dead letter queue, or an alert. Silently dropping a failed write after
five attempts is how an integration ends up quietly missing orders.