ErrLookupBackground articles › "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained

"API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained

"API error: {status}", "HTTP 401", "failed to fetch ... 403", "Poll failed: 500" — this family covers every error a library raises when an HTTP request comes back with a non-success status: the server, or a proxy in front of it, answered, but with 401, 403, 404, 429, 5xx, or another code outside the range the code accepts. Developers meet it wherever a library wraps HTTP: LLM proxy telemetry, CLI update checkers, OAuth discovery, messaging and sync APIs. The status and body carried in (or left out of) the message usually name the real cause — expired credentials, a wrong endpoint, rate limiting, an upstream outage — and this article maps the causes and fixes shared across the family, drawn from 122 documented records in 27 repositories.

Distilled from 122 documented records across 27 repositories.

Background

Every error in this family is produced by application-level HTTP client code after a request completes: name resolution, the connection, and the TLS handshake all succeeded, and a response arrived, just not with a status the library accepts. That is what separates it from connection and DNS failures, where no response ever comes back. The check itself is one line (response.ok, a status != 200 comparison, raise_for_status()), and the family exists because hundreds of libraries hand-roll the same wrapper that turns a status code plus a response body into an exception, a bail, or a console warning. Even the definition of success varies: most records accept any 2xx, some accept exactly 200 (litellm's PostHog and TogetherAI handlers turn 201/202/204 replies, and even 3xx redirects, into errors), and one deliberately tolerates a specific failure (openhuman accepts 405 on an MCP session delete).

What the caller sees varies more than the mechanism. Some errors embed status and body together (CodeWhale's update fetcher, turso's TursoException, zeroclaw's Lark send gate, claude-mem's worker and sync errors), so the log line itself names the cause. Others carry only the status (zeroclaw's Linq "API error: {status}", cc-switch's and airi's "HTTP {status}"), only the statusText (impeccable's poll and status probes, dawarich's points fetch, where statusText is often an empty string over HTTP/2 and the message comes out blank), or only the raw body (litellm's TogetherAI rerank, OpenMeter, DeepEval). At least one drops the HTTP detail entirely (docuseal's "Failed to start KBA"). When the message omits the cause, the cause usually still exists somewhere: zeroclaw records the real error body as a log attribute on the line above the bail, and most other cases reproduce with a single curl against the same URL.

Library architecture decides when the error fires. Some projects funnel every remote failure through one wrapper (turso's remote client, zeroclaw's shared gate for all Lark calls), so a single message shape covers auth, routing, and capacity alike. Others retry before surfacing: k6's provisioning SDK retries 5xx, and CodeWhale's updater retries 5xx, 408, and 429 in a bounded loop, so an error that reaches the caller means either a 4xx or an exhausted retry budget. Several records sit on telemetry paths (litellm's PostHog, OpenMeter, and DeepEval loggers) where the failure is logged and the main operation still succeeds, which makes these errors data loss rather than downtime. Vendored code adds a final trap: litellm's vendored DeepEval client never returns the httpx response, so its status-handling branch is dead code until patched.

From the caller's side the status works as a triage table: 401/403 point at credentials and scopes, 400/404 at payload and URL, 429/5xx at capacity and server health. Intermediaries blur the table. SSL-inspecting proxies answer 403 or 502 with their own HTML pages, reverse proxies cut deliberately idle long polls into 504s (impeccable), gateways return JSON bodies in a shape client parsers do not expect (docuseal's KBA fallback), and an http URL answered by a 301 to https reads as an error under exactly-200 checks (litellm's PostHog logger). The discipline that holds across all 30 records: recover the status and body, work out which side produced them, and only then choose between fixing configuration, fixing the payload, or backing off and retrying.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 102 more across the corpus — use search.

Honest provenance: generated on 2026-08-23 from AI-assisted analysis of the linked records. See how records are made.