Fix 429 rate-limit retries by honoring the Retry-After header
The problem
A burst of calls to an LLM gateway started returning:
Error code: 429 - {'error': {'message': 'Rate limit reached for requests. Please try again in 32s.'}}
with header retry-after: 32. My exponential backoff retried at 1s, 2s, 4s — every retry landing inside the window, counting against the limit again, until the key was temporarily blocked.
What didn't work
time.sleep(2 ** attempt)— ignores the exact number the server handed you.- Tenacity's default
wait_exponential— same mistake with nicer stack traces. - Sleeping a fixed 60s "to be safe" — works but triples the latency of every throttled batch.
The fix
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests
def get(url: str, max_retries: int = 5) -> requests.Response:
resp = None
for attempt in range(max_retries + 1):
resp = requests.get(url, timeout=30)
if resp.status_code != 429:
return resp
if attempt == max_retries:
resp.raise_for_status()
raw = resp.headers.get("Retry-After")
if raw and raw.isdigit():
wait = int(raw) # seconds until the window resets
elif raw: # HTTP-date form: "Wed, 05 Aug 2026 12:00:00 GMT"
retry_at = parsedate_to_datetime(raw)
wait = max(0, (retry_at - datetime.now(timezone.utc)).total_seconds())
else:
wait = min(2 ** attempt, 60) # server gave no hint: capped backoff
time.sleep(wait + 0.5) # small jitter margin past the reset
return resp
Why it works
Retry-After is the server telling you the exact instant its quota window resets; sleeping until then means the very first post-window request succeeds, instead of burning attempts inside the window and tripping the breaker into a harder block.