API Error Troubleshooting Directory: OpenAI, Anthropic, Gemini, and Cursor in One Place
When an AI request fails at 2 a.m., you do not want an essay. You want to find your error, learn whether it is your fault, and know whether to retry, fix something, or wait. This directory organizes the common failures across OpenAI, Anthropic, Gemini, and Cursor three ways: by status code, by provider, and by symptom. Each entry links to a full fix page, and a tested triage helper at the end turns the same logic into code.
Jump to your provider
The 60-second triage
- Read the status code and the error type. Both are in the response body. The message text is for humans and changes over time, so match on the code or type.
- Decide whose problem it is. 400-range means your request, credentials, permissions, or limits. 500-range, and Anthropic's 529, means the provider.
- Decide retry, fix, or wait. The table below gives the default for every status.
- Check the provider's status page if you see 500, 503, 529, or unexplained timeouts.
- Save the evidence: timestamp, model, status, error body, and the request ID header when one is returned.
Directory by status code
| Status | Usually means | Retry? | First thing to do |
|---|---|---|---|
| 400 | Malformed or invalid request; sometimes a billing or region precondition | No | Read the error message for the field at fault; validate parameters |
| 401 | Missing, wrong, or revoked credentials | No | Check the key value your app really sends, header name, and environment |
| 403 | Valid credentials without permission, or a blocked region | No | Check project, model access, API enablement, and location |
| 404 | Unknown model or endpoint | No | Verify the model name and API version |
| 413 | Request too large | No | Shorten input or split the request |
| 422 | Well-formed but semantically invalid (some gateways) | No | Check schema and parameter combinations |
| 429 | Rate limit, capacity, or an exhausted quota | Depends | Rate limit: back off. Quota or billing: add credit or raise the limit |
| 500 | Internal provider error | Yes | Retry with backoff; check status page |
| 502, 504 | Bad gateway or timeout between services | Yes | Retry; reduce request size or use streaming for long outputs |
| 503 | Service unavailable or model overloaded | Yes | Backoff, then fail over if it persists |
| 529 (Anthropic) | API temporarily overloaded | Yes | Backoff with jitter; consider a fallback model or provider |
| No response | Timeout, dropped connection, proxy or DNS problem | Yes | Check network path; see streaming failures |
How each provider labels errors
The statuses are shared, but the machine-readable labels differ. Match on these, not on message text.
| Status | Anthropic error.type | OpenAI error.code / type | Gemini error.status |
|---|---|---|---|
| 400 | invalid_request_error | invalid_request_error | INVALID_ARGUMENT, FAILED_PRECONDITION |
| 401 | authentication_error | invalid_api_key or an incorrect-key message | UNAUTHENTICATED |
| 403 | permission_error | Permission or unsupported-region errors | PERMISSION_DENIED |
| 404 | not_found_error | model_not_found or not found | NOT_FOUND |
| 413 | request_too_large | Context length or size errors | INVALID_ARGUMENT |
| 429 | rate_limit_error | rate_limit_exceeded or insufficient_quota | RESOURCE_EXHAUSTED |
| 500 | api_error | server_error | INTERNAL |
| 503 / 529 | overloaded_error (529) | Service unavailable (503) | UNAVAILABLE (503) |
| 504 | Timeout behavior varies; use streaming for long outputs | Gateway timeout | DEADLINE_EXCEEDED |
Names and messages evolve with API versions, so confirm against current provider documentation when something looks different from this table.
Anthropic (Claude) errors
| Error | Likely cause | Fix page |
|---|---|---|
401 authentication_error | Missing, malformed, or wrong key | Anthropic 401 authentication error |
403 permission_error | Key lacks access to the model or feature | Anthropic 403 permission error |
429 rate_limit_error | Requests or tokens per minute exceeded | Anthropic 429 too many requests |
| 429 organization limit | Shared organization-wide ceiling reached | Anthropic organization rate limit |
| Daily token limit | Tokens-per-day budget used up | Anthropic tokens per day limit |
529 overloaded_error | Temporary platform-wide load | Anthropic 529 overloaded |
| Key revoked | Key disabled, rotated, or exposed | Anthropic API key revoked |
Anthropic can also send an error event inside a stream after a 200 status, so streaming code must check for it. Full provider index: all Anthropic errors.
OpenAI errors
| Error | Likely cause | Fix page |
|---|---|---|
| 401 incorrect API key | Wrong or mistyped key, or the wrong header | OpenAI 401 incorrect API key |
| 401 invalid API key | Key deleted, revoked, or from another account | OpenAI 401 invalid API key |
| 403 region not supported | Request originates from an unsupported location | OpenAI 403 country or region blocked |
429 rate_limit_exceeded | Requests or tokens per minute exceeded | OpenAI 429 rate limit exceeded |
| 429 tokens per minute | Token budget for the minute used up | OpenAI 429 tokens per minute |
| 429 requests per day | Daily request cap reached | OpenAI 429 requests per day |
429 insufficient_quota | Credit or spend limit exhausted. Waiting does not help | OpenAI 429 rate limit exceeded |
| 503 service unavailable | Provider overload or maintenance | OpenAI 503 service unavailable |
| Invalid organization | Organization header or ID does not match the key | OpenAI invalid organization |
The same 429 status covers both a rate limit, where waiting works, and an exhausted quota, where it does not. Read the error code before deciding to retry. Full provider index: all OpenAI errors.
Google Gemini errors
| Error | Likely cause | Fix page |
|---|---|---|
401 UNAUTHENTICATED | Missing or invalid credentials | Gemini 401 unauthenticated |
| Invalid API key | Key mistyped, restricted, or deleted | Gemini invalid API key |
403 PERMISSION_DENIED | API not enabled, key restrictions, or no access to the model | Gemini 403 permission denied |
429 RESOURCE_EXHAUSTED | Per-minute or daily limit, or shared capacity | Gemini 429 resource exhausted |
| Quota exhausted | Project quota used up for the window | Gemini quota exhausted |
| Requests-per-day limit | Daily request cap reached | Gemini requests per day limit |
503 UNAVAILABLE | Model overloaded | Gemini 503 model overloaded |
| Service account key error | Bad, expired, or mis-scoped service account credentials | Gemini service account key error |
Gemini limits depend on the model and your plan, and a 429 can reflect either your own quota or temporary shared capacity. Full provider index: all Gemini errors.
Cursor errors: find the layer first
Cursor sits between you and a model, so an error you see in the editor may come from any of three layers. Naming the layer is most of the fix, and the wording of messages changes between versions, so diagnose by layer instead of by text.
| Layer | Typical symptoms | How to check | Fix |
|---|---|---|---|
| Editor and local network | Spinner that never ends, responses that cut off, works on one network but not another | Try another network without VPN or proxy; check the app's network and proxy settings | Update the app; adjust proxy or compatibility settings where offered; allow streaming through your firewall |
| Vendor backend and plan | Usage or plan limit messages, a model that is unavailable on your plan, a service incident | Check your account's usage page and the vendor's status page | Switch model, wait for the window to reset, or change plan |
| Model provider (your own key) | 401, 403, 429, 503 passed through from OpenAI, Anthropic, or Google | Run the same key in curl (see below) | Use the provider pages in this directory |
- Are you using the vendor's included models or your own API key? With your own key, provider errors pass through and this directory applies directly. With included models, limits come from your plan.
- Test the key outside the editor. If curl works and the editor does not, the problem is local: settings, network, or an outdated app.
- Check both status pages: the vendor's and the model provider's.
- Isolate the network. Corporate proxies and VPNs that buffer or interrupt streaming responses are a frequent cause of half-finished replies. See debugging streaming failures.
- Reduce the request. Very large contexts trigger size and token limits. Start a fresh conversation or attach fewer files.
- Collect details before contacting support: app version, model, time, the exact message, and the request ID if one is shown.
Directory by symptom
| Symptom | Likely family | Start here |
|---|---|---|
| "Invalid" or "incorrect" API key | Authentication | OpenAI, Anthropic, Gemini |
| Worked yesterday, 401 today | Key revoked or rotated | Anthropic revoked key |
| "Not supported in your country" | Region block | OpenAI region block |
| Too many requests, but traffic is low | Token, concurrency, or organization-level limit | Anthropic org limit, OpenAI tokens per minute |
| Errors only late in the day | Daily quota | Anthropic daily tokens, OpenAI daily requests, Gemini daily requests |
| Random 5xx during busy hours | Provider overload | Anthropic 529, OpenAI 503, Gemini 503 |
| Key is fine but access denied | Permission, project, or API enablement | Gemini 403, Anthropic 403 |
| Organization or project mismatch | Wrong organization header or ID | OpenAI invalid organization |
| Reply starts, then stops mid-answer | Streaming drop, stall, or output limit | Streaming failures guide |
| Agent keeps repeating the same call | Loop, not an API error | Runaway tool calls |
A 30-second direct test
When you are unsure whether the problem is your app or the provider, send the smallest possible request from a terminal. -i prints the status line and headers, including any request ID and Retry-After. Set MODEL to a model your account can use.
# Anthropic
curl -i https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"'"$MODEL"'","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'
# OpenAI
curl -i https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" -H "content-type: application/json" \
-d '{"model":"'"$MODEL"'","messages":[{"role":"user","content":"ping"}]}'
# Gemini
curl -i "https://generativelanguage.googleapis.com/v1beta/models/$MODEL:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" -H "content-type: application/json" \
-d '{"contents":[{"parts":[{"text":"ping"}]}]}'
If this succeeds, the provider and key are fine, and the bug is in your application's request, configuration, or network path. If it fails, the status and error body in the output point to the right page above.
A triage helper you can drop in
The same decisions as code: given a provider, a status, the response body, and headers, it returns whether to retry, how long to wait, a hint, and the matching fix page. It tells a rate limit from an exhausted quota on OpenAI, recognizes the region block, and treats network failures as retryable.
from typing import Optional
PROVIDER_DIR = {"anthropic": "anthropic", "openai": "openai", "gemini": "google-gemini"}
def page(provider: str, slug: str) -> str:
return f"/errors/{PROVIDER_DIR[provider]}/{slug}/"
def error_code(provider: str, body: Optional[dict]) -> Optional[str]:
err = (body or {}).get("error") or {}
if provider == "anthropic":
return err.get("type") # e.g. rate_limit_error, overloaded_error
if provider == "openai":
return err.get("code") or err.get("type") # e.g. insufficient_quota
return err.get("status") # Gemini: RESOURCE_EXHAUSTED, UNAVAILABLE...
def retry_after(headers: Optional[dict]) -> Optional[float]:
h = {k.lower(): v for k, v in (headers or {}).items()}
try:
return float(h["retry-after"])
except (KeyError, TypeError, ValueError):
return None
def classify(provider: str, status: Optional[int], body: Optional[dict] = None,
headers: Optional[dict] = None) -> dict:
code = error_code(provider, body)
message = ((body or {}).get("error") or {}).get("message", "") or ""
wait = retry_after(headers)
def out(kind, retryable, hint, slug=None):
return {"kind": kind, "retryable": retryable, "wait_s": wait, "hint": hint,
"page": page(provider, slug) if slug else None}
if status is None:
return out("network", True, "No response: timeout or dropped connection. Retry with backoff; "
"check proxy, VPN, and streaming settings.")
if status == 401:
slug = {"anthropic": "anthropic-401-authentication-error",
"openai": "openai-401-invalid-api-key" if code == "invalid_api_key"
else "openai-401-incorrect-api-key",
"gemini": "gemini-401-unauthenticated"}[provider]
return out("auth", False, "Credentials are missing, wrong, or revoked. Do not retry unchanged.", slug)
if status == 403:
if provider == "openai" and ("country" in message.lower() or "country" in str(code)):
return out("region", False, "Requests from this country or region are not supported.",
"openai-403-country-region-blocked")
slug = {"anthropic": "anthropic-403-permission-error", "openai": None,
"gemini": "gemini-403-permission-denied"}[provider]
return out("permission", False, "The key is valid but not allowed to do this. Check project, "
"model access, and API enablement.", slug)
if status in (400, 404, 413, 422):
hint = "Fix the request: parameters, model name, endpoint, or size. Retrying unchanged will fail again."
if provider == "gemini" and code == "FAILED_PRECONDITION":
hint = "Billing or region requirement not met for this model or plan."
return out("fix_request", False, hint)
if status == 429:
slug = {"anthropic": "anthropic-429-too-many-requests",
"openai": "openai-429-rate-limit-exceeded",
"gemini": "gemini-429-resource-exhausted"}[provider]
if provider == "openai" and code == "insufficient_quota":
return out("billing", False, "Quota or credit exhausted. Waiting will not help; add credit "
"or raise the spend limit.", slug)
return out("rate_limit", True, "Rate limit or capacity. Back off with jitter and honor Retry-After. "
"If it persists all day, check daily quotas.", slug)
if status in (500, 502, 503, 504, 529):
slug = {"anthropic": "anthropic-529-overloaded-error" if status == 529 else None,
"openai": "openai-503-service-unavailable" if status == 503 else None,
"gemini": "gemini-503-model-overloaded" if status == 503 else None}[provider]
return out("server", True, "Provider-side failure or overload. Retry with backoff; "
"fail over if it persists.", slug)
return out("unknown", False, "Unrecognized status. Log the full body and check the provider docs.")
And the tests that pin its behavior:
def test_network_failures_are_retryable():
r = classify("openai", None)
assert r["kind"] == "network" and r["retryable"]
def test_auth_pages_per_provider():
assert classify("anthropic", 401)["page"] == "/errors/anthropic/anthropic-401-authentication-error/"
assert classify("openai", 401, {"error": {"code": "invalid_api_key"}})["page"].endswith("openai-401-invalid-api-key/")
assert classify("openai", 401, {"error": {"code": "other"}})["page"].endswith("openai-401-incorrect-api-key/")
assert classify("gemini", 401)["page"] == "/errors/google-gemini/gemini-401-unauthenticated/"
assert not classify("gemini", 401)["retryable"]
def test_openai_region_block_is_not_a_generic_403():
r = classify("openai", 403, {"error": {"message": "Country, region, or territory not supported"}})
assert r["kind"] == "region" and not r["retryable"]
assert classify("anthropic", 403)["kind"] == "permission"
def test_429_splits_rate_limits_from_billing():
quota = classify("openai", 429, {"error": {"code": "insufficient_quota"}})
assert quota["kind"] == "billing" and not quota["retryable"]
rate = classify("openai", 429, {"error": {"code": "rate_limit_exceeded"}}, {"Retry-After": "7"})
assert rate["kind"] == "rate_limit" and rate["retryable"] and rate["wait_s"] == 7.0
assert classify("gemini", 429, {"error": {"status": "RESOURCE_EXHAUSTED"}})["page"].endswith("gemini-429-resource-exhausted/")
def test_overload_and_server_errors_retry():
assert classify("anthropic", 529, {"error": {"type": "overloaded_error"}})["page"].endswith("anthropic-529-overloaded-error/")
assert classify("gemini", 503)["page"].endswith("gemini-503-model-overloaded/")
assert classify("openai", 500)["retryable"] and classify("openai", 500)["page"] is None
def test_request_errors_are_not_retried():
for status in (400, 404, 413, 422):
assert not classify("anthropic", status)["retryable"]
assert "Billing" in classify("gemini", 400, {"error": {"status": "FAILED_PRECONDITION"}})["hint"]
def test_unknown_status_and_bad_headers():
assert classify("openai", 418)["kind"] == "unknown"
assert classify("openai", 429, headers={"retry-after": "soon"})["wait_s"] is None
Use the result to drive your retry policy, as described in the Bulletproof Claude API Wrapper post, and to decide when to fail over, as covered in the provider-agnostic client post.
What to log for every failure
- Timestamp, provider, model, and which environment or key produced the call.
- HTTP status and the machine-readable error type or code.
- The full error body, with secrets redacted.
- Retry count, total latency, and whether a fallback was used.
- The request ID header when the provider returns one. It is the first thing support will ask for.
Common mistakes this directory helps you avoid
- Retrying a 401, 403, or 400. It cannot succeed and may trigger abuse protections.
- Waiting out an exhausted quota. A billing 429 does not reset in seconds.
- Matching on message text. Messages change. Codes and types are the stable interface.
- Blaming the provider for a key problem, or the key for an outage. Check the status first.
- Debugging inside the IDE or framework before testing the raw API with curl.
- Failing over on request-shape errors. A bad request fails on every provider.
Frequently asked questions
How do I tell whether an error is my fault or the provider's?
Start with the status code. In the 400 range the request or your account is the problem: bad parameters, credentials, permissions, or limits. In the 500 range, plus Anthropic's 529, the provider is struggling and the same request will usually work later. Rate limits (429) sit in between: your request is fine, but your usage or the shared capacity is the issue.
Which errors should I retry automatically?
Retry timeouts, dropped connections, 429s that are rate limits, and 500, 502, 503, 504, and 529 responses, using exponential backoff with jitter and honoring Retry-After. Never retry 400, 401, 403, 404, or 413 unchanged, and never retry a 429 that is really an exhausted billing quota.
Why do I get a 429 when I am barely sending any requests?
Limits are measured on more than request count. Tokens per minute, tokens per day, concurrent requests, and organization-wide or project-wide quotas can all trigger a 429, and a few large requests can use up a tokens-per-minute budget. Another service sharing your key or organization can also be consuming the quota.
What is the difference between a rate limit and an exhausted quota?
A rate limit is a short-term ceiling that resets within seconds or minutes, so waiting works. An exhausted quota or billing limit does not reset on its own, so waiting does nothing until you add credit, raise the limit, or the daily or monthly window rolls over. OpenAI uses the same 429 status for both and tells them apart by the error code.
Why does my key work in curl but fail in my app?
Usually the app is sending something different from what you tested: a different key from a stale environment variable, extra whitespace or quotes in the value, a wrong header name, an organization or project mismatch, or a request from a region or network that the provider blocks. Print a masked version of the key your app actually loads, and compare headers byte for byte.
How do I find out which layer is failing in an IDE like Cursor?
Separate the layers: the editor, the vendor's backend, and the model provider. If you use your own API key, test that key directly with curl. If the direct call works, the problem is in the editor, its settings, or its network path. If it fails too, the error is the provider's and the provider's error page applies.
Do error messages and codes stay the same between versions?
Not always. Providers add, rename, and retire error types and messages, and they differ between API versions and between SDKs. Match on the status code and the machine-readable error type or code, not on message text, and check the provider's current documentation when something changes.
What should I log for every failed call?
The timestamp, provider, model, HTTP status, the error type or code, the full error body, the retry count, the latency, and the request ID header when the provider returns one. With those fields, support teams and your own on-call can diagnose most failures without reproducing them.
When should I fail over to another provider?
When the failure is on the provider's side and persists past a couple of retries, such as repeated 503, 529, or timeouts, or when a rate limit would make you wait longer than your latency budget. Do not fail over on authentication or request-shape errors, since they will fail on the next provider too.
How often does this directory change?
Provider error behavior changes regularly, so each linked page is reviewed against current provider documentation. When a message or limit here differs from what you see, trust the live response body and the provider's own documentation, and treat this directory as the fastest route to the right explanation.