Codegen Generated Retry Interceptors
Quick answer: A generated retry interceptor sits between the client method and the HTTP layer, catches a failed call, and re-sends it when the request is safe and the failure looks recoverable. Good defaults limit retries to idempotent methods and transient status codes, use exponential backoff with jitter, and cap the attempt count. The generator gives you the mechanism, not a guarantee; you still have to tune it to the API's idempotency and rate-limit behavior.
What the interceptor does
The interceptor sees each request before it leaves and each response or error on the way back. On a failure it decides whether to retry, waits for the backoff window, and re-issues the same request without the calling code knowing a retry happened. Keeping this in a single interceptor means the retry policy is applied consistently across every generated operation instead of being re-implemented per call.
What is safe to retry
Not every failure should be re-sent. A client error such as 400, 401, or 404 will not improve on a second attempt, while a 429, 503, or a connection timeout might. The method matters too: GET and HEAD are safe to repeat, and PUT or DELETE are usually idempotent, but a plain POST can create a duplicate resource unless the API supports an idempotency key.
| Failure | Retry by default | Reason |
|---|---|---|
| 429 with Retry-After | Yes | Rate limit, wait the given time |
| 503 | Yes | Transient server state |
| Connection timeout | Cautiously | Server may have processed it |
| 400/401/404 | No | Client error, retry will not help |
| POST without idempotency key | No | Risk of a duplicate write |
Backoff and the hard stop
Retrying immediately after a failure turns one problem into a burst of load against a struggling server. Exponential backoff with jitter spaces the attempts out, and when the response carries a Retry-After header that value is the authority for how long to wait. A maximum attempt count and a total time budget keep the client from looping forever, and the final error should surface after the budget is exhausted rather than hanging the caller.
Verifying the behavior with a mock
A retry path is hard to observe against a healthy API because it never fires. Point the generated client at a local mock that returns 429 twice and then 200, and run an ordered scenario that records each request; Powerduck can serve that mock and assert the client waited, retried the same call, and stopped once it succeeded, while a separate non-idempotent POST is confirmed to fail rather than re-send.
When retries meet a circuit breaker
Retries and a circuit breaker solve different halves of the same problem. Retries handle a single call that hits a brief failure; the breaker watches the failure rate across many calls and opens when the dependency is clearly down, at which point the interceptor should stop sending requests it knows will fail. After a cooldown the breaker moves to a half-open state and lets one trial request through; success closes it, failure opens it again. Layering the two keeps a client from burning its retry budget against an already-failed service while still recovering automatically once the dependency comes back.
Honest limits
The interceptor cannot always tell whether a timed-out request reached the server, so retrying it may duplicate an operation the server already completed. The generator does not know your business semantics, such as which POSTs carry an idempotency key, and that has to be configured explicitly. Retries also hide transient failures from callers and add tail latency, and a fleet of clients retrying at once can amplify an outage unless jitter and a circuit breaker are in place.
Frequently asked questions
Does the generator enable safe retries out of the box? It provides the mechanism and sensible defaults, but you should confirm which methods and status codes match your API.
Should a POST ever be retried? Only when the API supports an idempotency key, so a repeated request does not create a second resource.
What wins, backoff or Retry-After? Retry-After, when present, sets the wait time.
Why add jitter? It keeps many clients from retrying in lockstep and forming a new burst.
The bottom line
A generated retry interceptor turns transient failures into successful calls only when it re-sends safe requests, respects backoff and Retry-After, and stops at a clear cap. Tune it to the API's idempotency and verify it with a mock. For more on generated clients, see the OpenAPI codegen guide hub.