Why a 429 is not one thing
A rate-limit response is a symptom. The useful diagnosis comes from the limit dimension, the reset evidence and the shape of the request that triggered it.
The response
429
A shared status code. It says the request was refused, not why.
The evidence
- Headers: remaining and reset
- Body: structured error details
- Request: model, tokens, service tier
Quota
Wait for reset or reduce the request.
Acceleration
Slow the ramp; the account may be bursting.
Capacity
Retry with bounded backoff or another route.
Start with the body, then the headers
A good adapter preserves the provider's original status, error type, message, structured details and retry hint before translating it into a common status. Otherwise “rate limited” becomes a bucket that hides the next action.
For example, Gemini's RESOURCE_EXHAUSTED can be associated with requests, tokens, daily limits or spend-based limits. Google recommends exponential backoff for retryable 429 and 503 responses, while its quota documentation says limits are project-level and model-specific. Read Gemini's limits ↗ · Read Gemini's retry guidance ↗
The five diagnoses
Retry
When retrying makes sense
Use the provider's retry-after when present. Otherwise use capped exponential backoff with jitter, and only retry idempotent work. Track the attempt count and final reason.
Stop
When retrying is noise
Do not loop on invalid keys, missing model access, a hard spend cap or a request that exceeds context. A 429 that repeats instantly across providers may be your traffic shape, not the market.
What to store
Provider, endpoint, model, project/workspace scope, HTTP status, error type, selected rate-limit headers, retry-after, request size, token usage, latency, generation/request ID and whether the request was retried. This is enough to explain most incidents without logging prompts.
Primary references: OpenAI ↗ · Anthropic ↗ · Groq ↗ · Mistral ↗.