API Error Troubleshooting Directory: OpenAI, Anthropic, Gemini, and Cursor in One Place | AI Error Hub
AI Error Hub/Blog/API Error Troubleshooting Directory
Reference · Troubleshooting

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.

ZK
Zain Khan
Editor, AI Error Hub · October 28, 2026 · 16 min read

Jump to your provider

The 60-second triage

  1. 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.
  2. Decide whose problem it is. 400-range means your request, credentials, permissions, or limits. 500-range, and Anthropic's 529, means the provider.
  3. Decide retry, fix, or wait. The table below gives the default for every status.
  4. Check the provider's status page if you see 500, 503, 529, or unexplained timeouts.
  5. Save the evidence: timestamp, model, status, error body, and the request ID header when one is returned.

Directory by status code

StatusUsually meansRetry?First thing to do
400Malformed or invalid request; sometimes a billing or region preconditionNoRead the error message for the field at fault; validate parameters
401Missing, wrong, or revoked credentialsNoCheck the key value your app really sends, header name, and environment
403Valid credentials without permission, or a blocked regionNoCheck project, model access, API enablement, and location
404Unknown model or endpointNoVerify the model name and API version
413Request too largeNoShorten input or split the request
422Well-formed but semantically invalid (some gateways)NoCheck schema and parameter combinations
429Rate limit, capacity, or an exhausted quotaDependsRate limit: back off. Quota or billing: add credit or raise the limit
500Internal provider errorYesRetry with backoff; check status page
502, 504Bad gateway or timeout between servicesYesRetry; reduce request size or use streaming for long outputs
503Service unavailable or model overloadedYesBackoff, then fail over if it persists
529 (Anthropic)API temporarily overloadedYesBackoff with jitter; consider a fallback model or provider
No responseTimeout, dropped connection, proxy or DNS problemYesCheck 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.

StatusAnthropic error.typeOpenAI error.code / typeGemini error.status
400invalid_request_errorinvalid_request_errorINVALID_ARGUMENT, FAILED_PRECONDITION
401authentication_errorinvalid_api_key or an incorrect-key messageUNAUTHENTICATED
403permission_errorPermission or unsupported-region errorsPERMISSION_DENIED
404not_found_errormodel_not_found or not foundNOT_FOUND
413request_too_largeContext length or size errorsINVALID_ARGUMENT
429rate_limit_errorrate_limit_exceeded or insufficient_quotaRESOURCE_EXHAUSTED
500api_errorserver_errorINTERNAL
503 / 529overloaded_error (529)Service unavailable (503)UNAVAILABLE (503)
504Timeout behavior varies; use streaming for long outputsGateway timeoutDEADLINE_EXCEEDED

Names and messages evolve with API versions, so confirm against current provider documentation when something looks different from this table.

Anthropic (Claude) errors

ErrorLikely causeFix page
401 authentication_errorMissing, malformed, or wrong keyAnthropic 401 authentication error
403 permission_errorKey lacks access to the model or featureAnthropic 403 permission error
429 rate_limit_errorRequests or tokens per minute exceededAnthropic 429 too many requests
429 organization limitShared organization-wide ceiling reachedAnthropic organization rate limit
Daily token limitTokens-per-day budget used upAnthropic tokens per day limit
529 overloaded_errorTemporary platform-wide loadAnthropic 529 overloaded
Key revokedKey disabled, rotated, or exposedAnthropic 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

ErrorLikely causeFix page
401 incorrect API keyWrong or mistyped key, or the wrong headerOpenAI 401 incorrect API key
401 invalid API keyKey deleted, revoked, or from another accountOpenAI 401 invalid API key
403 region not supportedRequest originates from an unsupported locationOpenAI 403 country or region blocked
429 rate_limit_exceededRequests or tokens per minute exceededOpenAI 429 rate limit exceeded
429 tokens per minuteToken budget for the minute used upOpenAI 429 tokens per minute
429 requests per dayDaily request cap reachedOpenAI 429 requests per day
429 insufficient_quotaCredit or spend limit exhausted. Waiting does not helpOpenAI 429 rate limit exceeded
503 service unavailableProvider overload or maintenanceOpenAI 503 service unavailable
Invalid organizationOrganization header or ID does not match the keyOpenAI 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

ErrorLikely causeFix page
401 UNAUTHENTICATEDMissing or invalid credentialsGemini 401 unauthenticated
Invalid API keyKey mistyped, restricted, or deletedGemini invalid API key
403 PERMISSION_DENIEDAPI not enabled, key restrictions, or no access to the modelGemini 403 permission denied
429 RESOURCE_EXHAUSTEDPer-minute or daily limit, or shared capacityGemini 429 resource exhausted
Quota exhaustedProject quota used up for the windowGemini quota exhausted
Requests-per-day limitDaily request cap reachedGemini requests per day limit
503 UNAVAILABLEModel overloadedGemini 503 model overloaded
Service account key errorBad, expired, or mis-scoped service account credentialsGemini 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.

LayerTypical symptomsHow to checkFix
Editor and local networkSpinner that never ends, responses that cut off, works on one network but not anotherTry another network without VPN or proxy; check the app's network and proxy settingsUpdate the app; adjust proxy or compatibility settings where offered; allow streaming through your firewall
Vendor backend and planUsage or plan limit messages, a model that is unavailable on your plan, a service incidentCheck your account's usage page and the vendor's status pageSwitch 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 GoogleRun the same key in curl (see below)Use the provider pages in this directory
  1. 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.
  2. Test the key outside the editor. If curl works and the editor does not, the problem is local: settings, network, or an outdated app.
  3. Check both status pages: the vendor's and the model provider's.
  4. 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.
  5. Reduce the request. Very large contexts trigger size and token limits. Start a fresh conversation or attach fewer files.
  6. Collect details before contacting support: app version, model, time, the exact message, and the request ID if one is shown.

Directory by symptom

SymptomLikely familyStart here
"Invalid" or "incorrect" API keyAuthenticationOpenAI, Anthropic, Gemini
Worked yesterday, 401 todayKey revoked or rotatedAnthropic revoked key
"Not supported in your country"Region blockOpenAI region block
Too many requests, but traffic is lowToken, concurrency, or organization-level limitAnthropic org limit, OpenAI tokens per minute
Errors only late in the dayDaily quotaAnthropic daily tokens, OpenAI daily requests, Gemini daily requests
Random 5xx during busy hoursProvider overloadAnthropic 529, OpenAI 503, Gemini 503
Key is fine but access deniedPermission, project, or API enablementGemini 403, Anthropic 403
Organization or project mismatchWrong organization header or IDOpenAI invalid organization
Reply starts, then stops mid-answerStreaming drop, stall, or output limitStreaming failures guide
Agent keeps repeating the same callLoop, not an API errorRunaway 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.

Keep reading