Fix AI API Errors: Quick Troubleshooting Guide

Every developer hits AI API errors in production. A 429 means your request was rate-limited. A 401 means your API key is wrong. A 500 means something broke on the provider’s side. This guide covers what each code means across OpenAI, Anthropic, and Google Gemini — and what to do about each one.

The Error Code Reference

The first distinction that matters: some errors are your fault (fix the code); others are the provider’s problem (retry with backoff).

Retryable (use backoff):
– 429 — Rate limit or quota exceeded. Wait and retry.
– 500 / 502 / 503 / 504 — Provider infrastructure issue. Wait and retry.

Not retryable (fix the code):
– 400 Bad Request — Malformed request. Check: context window exceeded, invalid model name, or bad JSON. Will not fix with retries.
– 401 Unauthorized — Invalid or expired API key. Update the key.
– 403 Forbidden — Key is valid but lacks permission. Check organization access settings.

Provider-specific codes (Anthropic):
– 402 Billing Error — Payment issue. Check Claude Console or AWS Marketplace.
– 409 Conflict — Concurrent modification conflict. Retry after resolving the conflict.
– 413 Request Too Large — Exceeds maximum request size. Reduce context.
– 529 Overloaded — Provider temporarily overloaded across all users. Retry with backoff.

Understanding Rate Limits: The 429 in Detail

Rate limits come in multiple simultaneous dimensions enforced at the same time:

RPM (Requests Per Minute) — API calls in a rolling 60-second window.

TPM (Tokens Per Minute) — Total tokens processed per minute.

Anthropic separates ITPM (input) and OTPM (output) as distinct limits — going over either returns a 429 that names the specific limit exceeded.

Monthly spend cap — Some plans enforce a monthly cap. A 429 with no retry-after header and the message “enforced_spend_limit_reached” means you hit the monthly cap. Retrying will not help. You need to wait until the first of next month UTC or request a tier increase.

Providers return headers that tell you exactly where you stand. OpenAI sends x-ratelimit-remaining-requests and x-ratelimit-reset-requests. Anthropic sends anthropic-ratelimit-requests-remaining and anthropic-ratelimit-requests-reset. The retry-after header, when present, tells you exactly how many seconds to wait — honor it.

Exponential Backoff: The Right Way to Retry

Basic retry logic fails because rate limits need time to clear. Retrying immediately makes it worse. The correct pattern is exponential backoff with jitter:

import time, random

def call_with_backoff(api_call_fn, max_retries=5, base_delay=1.0, max_delay=60.0):
“””
Call an API function with exponential backoff and jitter.
Jitter prevents synchronized retry storms across clients.
“””
last_exception = None

for attempt in range(max_retries):
try:
return api_call_fn()
except Exception as e:
last_exception = e
error_str = str(e).lower()

# Only retry rate limits and server errors
is_retryable = any(x in error_str for x in [
‘429’, ‘rate limit’, ‘500’, ‘502’, ‘503’, ‘504’, ‘overloaded’, ‘timeout’
])

if not is_retryable:
raise # Configuration error — don’t retry

if attempt == max_retries – 1:
break

# Exponential backoff with random jitter
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1)
actual_delay = delay + jitter

print(f”Retry {attempt + 1} in {actual_delay:.1f}s…”)
time.sleep(actual_delay)

raise last_exception

The jitter is important — without random variation, all retrying clients retry at exactly the same time. Jitter breaks this up.

If the response includes a retry-after header, use that value directly — it is more accurate than exponential backoff.

Provider-Specific Error Patterns

Claude API — 429 rate_limit_error:

Claude returns a 429 that names the specific limit exceeded. The error body looks like:

{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "rate limit exceeded: 1000 requests per minute"
  }
}

The retry-after header tells you exactly how long to wait. The Anthropic SDK retries automatically twice by default.

If the message contains “enforced_spend_limit_reached” with no retry-after, you hit the monthly spend cap. Stop retrying. Check Claude Console to request a tier increase, or wait until 00:00 UTC on the first of the next month.

OpenAI — 429 subtypes:

OpenAI’s 429 responses include a specific error code:
– rate_limit_exceeded — RPM or TPM exceeded. Retry after the retry-after delay.
– insufficient_quota — Key has exceeded its allocated quota. Check your billing.
– credit_balance_exhausted — No prepaid credits. Add credits in API billing settings.
– organization_usage_limit_exceeded — Organization usage limit reached. Request a higher limit.

Billing quota errors (unlike rate limit errors) do not resolve with retries. Address the billing issue first.

Google Gemini — 429 RESOURCE_EXHAUSTED:

Gemini returns a 429 with message “RESOURCE_EXHAUSTED” and indicates which quota metric was exceeded. Google does not always include a Retry-After header. Use exponential backoff with a conservative initial delay (30–60 seconds).

Proactive Rate Limit Management

Waiting for 429s to happen is the slowest approach. Track your usage and pause before each request:

import time, threading

class RateLimitTracker:
“””Track rolling window usage to avoid rate limit errors before they occur.”””

def __init__(self, max_rpm=500, max_tpm=200000, window_seconds=60):
self.max_rpm = max_rpm
self.max_tpm = max_tpm
self.window = window_seconds
self._requests = []
self._lock = threading.Lock()

def _cleanup(self):
cutoff = time.time() – self.window
self._requests = [(t, n) for t, n in self._requests if t > cutoff]

def can_send(self, estimated_tokens=1000):
with self._lock:
self._cleanup()
rpm = len(self._requests)
tpm = sum(n for _, n in self._requests)
return rpm < self.max_rpm and tpm + estimated_tokens < self.max_tpm

def record(self, tokens_used):
with self._lock:
self._requests.append((time.time(), tokens_used))

def wait_time(self):
with self._lock:
self._cleanup()
if not self._requests:
return 0.0
oldest = min(t for t, _ in self._requests)
return max(0.0, self.window – (time.time() – oldest))

Use this before sending, not after catching a 429. For batch pipelines processing hundreds of requests, this prevents errors entirely.

What Is Not an API Error: Content Refusal

When an AI model refuses content due to safety policies, it returns HTTP 200 OK — not an error. The refusal appears in the response body:

Anthropic refusal:

{"type": "message", "content": [{"type": "refusal", "text": "I can't help with that."}]}

OpenAI content filter:

{"choices": [{"content_filter": {"filtered": true, "reason": "harassment"}}]}

This is not a bug and cannot be fixed with retries. Retrying the same request with the same content will produce the same refusal. Adjust the prompt.

Frequently Asked Questions

Q: My 429 has no retry-after header. What do I do?

If it’s from Anthropic and the message contains “enforced_spend_limit_reached”, you hit the monthly spend cap — no retry will succeed. For other cases, use exponential backoff with a conservative initial delay (30–60 seconds).

Q: Should I always retry 5xx errors?

Yes, with a longer initial delay than rate limit errors (30–60 seconds rather than 1–2 seconds). Provider infrastructure issues often take 30–60 seconds to fully clear. Use backoff, not immediate retry.

Q: How do I distinguish a rate limit 429 from a billing 429?

Rate limit 429s carry a retry-after header and indicate temporary limits. Billing 429s (insufficient_quota, credit_balance_exhausted, enforced_spend_limit_reached) do not carry retry-after and will not resolve with retries. Check the error message and error code before deciding to retry.

Q: My application makes thousands of API calls. How do I avoid constant 429s?

Use a proactive rate limit tracker that maintains a rolling window of your request timestamps and token usage. Check before each send whether you have headroom under both RPM and TPM limits. Also use caching for repeated or similar queries — a cache hit costs nothing and hits zero rate limits.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top