Skip to content
throttle
Menu
HomeGuidesHandle HTTP 429 Responses
Reliability guide

How to Handle HTTP 429 Responses

A 429 response is a control signal. Good clients pause the right work, honor server guidance, spread retries, and stop retrying when success is no longer useful.

Key takeaways

  • Treat 429 as an expected operating condition, not an invitation to retry immediately.
  • Honor Retry-After when it is present and understand provider-specific rate-limit headers.
  • Use capped exponential backoff with jitter and a strict retry limit.
  • Queue or defer non-urgent work, and make retried operations idempotent.
  • Log the provider, endpoint, attempt, wait, and request correlation details.

1. Understand what the response means

HTTP 429 means the server believes the client has sent too many requests within a relevant period. The server decides how requests are counted. A limit might apply to an IP address, user, token, organization, endpoint, or a more complex resource bucket.

Do not assume every provider uses the same window or headers. Read the provider's documentation and capture the actual response headers before designing retry behavior.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1783965600

2. Respect Retry-After

Retry-After tells a client how long it should wait before a follow-up request. Depending on the server, the value can be a delay in seconds or an HTTP date. Provider documentation may narrow that behavior for a particular API.

When Retry-After is valid, treat it as the earliest permitted retry time. A local backoff policy may choose a longer delay, but it should not choose a shorter one.

  • Parse both documented forms safely.
  • Reject negative, malformed, or unreasonable values according to a defined policy.
  • Use a monotonic clock for elapsed waits where the runtime provides one.
  • Do not block a request thread when the job can be returned to a delayed queue.

3. Add bounded backoff and jitter

If the server does not provide a usable retry time, increase the delay between attempts. Exponential backoff reduces repeated pressure, while jitter prevents many clients from waking at the same instant.

Every retry policy needs a maximum delay, maximum attempt count, and total time budget. Without those boundaries, a retry loop can outlive the work it was meant to complete.

server_delay = parsed Retry-After, when present
local_delay  = min(max_delay, initial_delay * multiplier^attempt)
wait_time    = max(server_delay, random(0, local_delay))
stop when attempts or total retry budget is exhausted

4. Separate urgent work from deferrable work

A user-facing request and a background synchronization job should not share one blind retry policy. If work is not urgent, place it in a delayed queue and resume it after capacity returns.

Priorities, concurrency limits, deduplication, and dead-letter handling keep a provider incident from turning into an internal queue incident.

  • Reserve limited capacity for interactive operations when the provider contract allows it.
  • Coalesce duplicate refresh work for the same resource.
  • Use idempotency keys or application-level deduplication for retried writes.
  • Expire work that is no longer valuable instead of retrying forever.

5. Make rate limiting observable

Record enough context to explain why traffic was delayed without logging secrets or sensitive payloads. Useful fields include provider, endpoint family, response status, relevant rate-limit headers, attempt number, computed wait, queue depth, and a request correlation identifier.

Alert on sustained exhaustion, growing queue age, and repeated final failures. A single handled 429 is usually not an outage. A workload that never regains capacity is.

Implementation checklist

  • Identify which credential, user, resource, or endpoint owns the limit bucket.
  • Read and validate provider-specific headers.
  • Honor Retry-After and reset times.
  • Apply bounded backoff with jitter.
  • Cap attempts and total retry duration.
  • Make writes idempotent before retrying them.
  • Queue non-urgent work and monitor queue age.
  • Test the failure path without sending uncontrolled traffic to a provider.

Sources and further reading