Troubleshooting
A 429 is not one error but three: rate limits, exhausted quota, and platform overload return different bodies and need different fixes. Diagnose the real cause in 30 seconds and copy a working exponential-backoff retry.
When you get 429 Too Many Requests, the first move is not to retry — it is to read the response body. A 429 has at least three distinct causes with incompatible fixes: blind retries make rate limiting worse, and topping up your balance does nothing for throughput limits.
| error.code / type | Meaning | Correct fix |
|---|---|---|
rate_limit_exceeded | Requests per minute (RPM) or tokens per minute (TPM) exceeded | Exponential backoff + lower concurrency |
insufficient_quota | Quota exhausted: empty balance, spent free tier, or billing not enabled | Retrying is useless — check balance and billing |
No body / slow_down | Platform-level overload or protective throttling | Wait per Retry-After; if persistent, switch model or route |
A typical throughput 429 looks like this — note error.code and the limit numbers in the message:
{
"error": {
"message": "Rate limit reached for gpt-4o in organization org-xxx on requests per min (RPM): Limit 500, Used 500.",
"type": "requests",
"code": "rate_limit_exceeded"
}
}A quota 429 instead has code: "insufficient_quota" and usually says “check your plan and billing details” — no amount of retrying will fix it.
import time, random
from openai import OpenAI, RateLimitError
client = OpenAI(base_url="https://zerofa.ai/v1", api_key="YOUR_API_KEY")
def chat_with_retry(messages, max_retries=5):
for attempt in range(max_retries):
try:
return client.chat.completions.create(
model="gpt-4o", messages=messages)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
retry_after = getattr(e, "response", None)
wait = float(retry_after.headers.get("retry-after", 0)) if retry_after else 0
wait = wait or (2 ** attempt) + random.random()
time.sleep(wait)insufficient_quota means an account-level issue: empty balance, expired free credits, or a failed payment method. Check balance, then billing status, then the usage dashboard for when quota hit zero. In code, alert on this instead of retrying.
If you call several providers, re-implementing retry rules for each of them is expensive. Through an OpenAI-compatible gateway, each API key gets its own configurable RPM and daily budget, failed calls are never billed, and switching models is just changing the model field. See error codes & billing rules, the 5-minute quickstart, and the model catalog.
Honor the Retry-After response header when present. Otherwise use exponential backoff: start at 1 second, double each attempt, add random jitter, and cap at 5 retries. Resending immediately just keeps resetting the rate-limit window.
Check error.code in the response body. rate_limit_exceeded means you exceeded requests or tokens per minute, which has nothing to do with balance. Only insufficient_quota is about billing. Throughput problems are fixed by lowering concurrency, not by topping up.
Provider rate limits are usually enforced per account or organization, not per key, so rotating keys under the same account does nothing and may violate the terms of service. Control concurrency, back off on retries, or use an aggregation gateway with per-key limits instead.
Yes. The 429 happens when the request is admitted, before any tokens stream. A rate-limited streaming call fails at connection time and should be classified and retried exactly like a non-streaming one.