ErrLookup › Background articles › HTTPError: what it means when a library throws an "HTTPError" (and how to fix it)
HTTPError: what it means when a library throws an "HTTPError" (and how to fix it)
"HTTPError" is the generic exception name many open-source libraries use to wrap any failed HTTP request — a non-2xx status, a failed retry loop, or an upstream service error rethrown in the library's own words. Developers meet it in Budibase, Bundler, yt-dlp, frp, and others whenever an HTTP call behind an SDK, importer, or fetcher fails and the library surfaces the failure as an HTTPError instead of a raw network exception.
Distilled from 332 documented records across 11 repositories.
Background
HTTPError is not one error but a naming convention: across these repositories it is the exception type a library raises when an HTTP request it made fails. The layer that produces it is usually a thin wrapper over a real HTTP client — Node fetch in Budibase's server, urllib or Bundler's Gem::Net::HTTP fetcher, the fetch() calls in frp's web dashboard, urllib in yt-dlp's networking stack. The wrapper inspects the response, decides it is not acceptable (non-2xx status, ok:false payload, missing context), and raises HTTPError, often carrying the upstream status code and sometimes the raw response body as the message.
What the caller sees varies widely because each library chooses what to embed. Some pass through the upstream text: yt-dlp's "HTTP Error {status}: {reason}", frp's bare "HTTP {status}", and Budibase's Gemini File Search wrapper, whose message is literally the upstream response body. Others use the message for their own validation result and use the HTTP status purely as a transport code — a large share of the Budibase records are HTTP 400 errors about request-shape problems (invalid datetime formats, missing workspaceId, malformed project-package manifests, non-UUID Teams appIds), not transport failures at all. In those cases HTTPError is best read as "the API rejected your request" rather than "the network broke".
A few records use the type for subtler jobs. nexu-io/open-design raises "Request failed with no error details" as a defensive tail of a retry loop — control fell through without any stored last_error, which usually means the retry count was below 1 or an exception type was not covered by the except handlers. Bundler's "Too many redirects" is a redirect-limit guard: the fetcher recurses on redirect responses until a configured hop limit is hit. Budibase deliberately throws a 404 (not 403) for "Agent log detail not found" so callers cannot probe for other agents' log entries — the HTTP status on the HTTPError is part of the library's intended semantics, so read it before deciding how to react.
Because the message text is so library-specific, the durable debugging approach is shared: capture the exception object, not just the string. Several libraries attach structured fields — yt-dlp exposes .status, .reason, .response, and .redirect_loop; frp's wrapper sets err.status; Budibase embeds upstream status codes in the message. Recovering those fields (and the response body where the stream is kept open, as yt-dlp's adapter does) is almost always the fastest route from "HTTPError" to the real cause.
Common causes
- Upstream server returned a non-2xx status.The most direct form: the HTTP call succeeded at the transport level but the server answered 4xx/5xx, and the library wraps it in HTTPError. Examples include yt-dlp's "HTTP Error {status}: {reason}" (403 bot checks, 404 removed media, 429 throttling), frp's "HTTP {status}" from the admin API, and Budibase's license client converting any non-200 from the licensing service into "Error getting license: {message}".
- Invalid or incomplete request payload rejected as 400.Many Budibase records throw HTTPError with status 400 for request-shape problems: datetimes not in ISO format, missing workspaceId context, unsupported AI configType values, non-UUID Teams appIds, unsupported group-by field types, or duplicate row-action names. The network is fine — the request body or context is wrong.
- Malformed or hand-edited package/content being imported.Budibase's project-import path raises a family of 400 HTTPErrors when the package's contents are inconsistent: invalid dependency-index.json shape, queries or row actions whose parent datasource/table is missing from the idMap, external table ids lacking the '<datasourceId>__' prefix, resource-count mismatches against project.json, or docs that fail to save. Hand-editing exports or exporting from incompatible versions triggers these.
- Redirect chain exceeded the configured limit.Bundler raises "Too many redirects" when its fetcher recurses through Gem::Net::HTTPRedirection responses past the redirect_limit from bundle-config's `redirect` setting. Mirror chains through corporate proxies, http→https stacks, or self-redirecting servers can exceed even sane limits; yt-dlp similarly derives a redirect_loop flag from urllib's 'redirect error'.
- Upstream authentication or authorization failure.Non-2xx statuses caused by missing or bad credentials surface as HTTPError with the upstream code embedded: LiteLLM 401s in Budibase's Gemini File Search wrapper, 401/403 from the licensing service, and 401/403 from frp's admin API when webServer.user/password are wrong.
- Transport failure during a library-initiated fetch.When the underlying fetch itself blows up — DNS failure, connection refused, TLS handshake error, timeout — some libraries wrap it rather than rethrowing the raw error. Budibase's fetchFromUrl converts non-HTTPError fetch failures into HTTPError with status 502 and the underlying message appended (ENOTFOUND, ECONNREFUSED, self-signed certificate).
- Defensive fallback after a retry loop found nothing.nexu-io/open-design's "Request failed with no error details" fires when the retry loop exits without assigning last_error — practically unreachable unless retries <= 0 or an uncovered exception type (e.g. a new ssl.SSLError path) escapes the except handlers. It signals a caller or transport contract violation more than a server problem.
What usually fixes it
- Read the structured fields on the exception before the message: most libraries attach the upstream status (err.status, e.status, e.reason) and sometimes the response body. The numeric status tells you whether this is your bug (4xx), the server's (5xx), or retryable (429/5xx).
- For 4xx errors, treat the message as validation feedback and fix the request: correct formats (ISO datetimes), required context (workspaceId), exact enum values imported from the library's types rather than hardcoded strings, and syntactically valid identifiers (UUIDs).
- For 5xx/429 from upstream services, retry with backoff honoring Retry-After where present, verify the upstream service is reachable and healthy from the same machine, and check credentials (API keys, license keys, admin passwords) when the status is 401/403.
- For import/package flows, regenerate packages with the library's own export tooling instead of hand-editing archives, keep source and target versions compatible, and pre-validate manifest counts, id prefixes, and JSON parseability before uploading.
- For redirect-related HTTPErrors, trace the chain (curl -sIL), raise the configured redirect limit (bundle config set redirect 10), and fix self-redirecting mirrors or point at the final URL directly.
- When catching HTTPError in your own code, log the embedded upstream status and body alongside the message so wrapper-only signals (like open-design's no-details fallback) never become the sole diagnostic.
Go deeper
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
- DNS resolution errors: ENOTFOUND and getaddrinfo failures — how hostname lookups fail and how to debug them.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Documented occurrences
- Request failed with no error details(nexu-io/open-design)
- A new verification key is required when changing the embed SSO algorithm(Budibase/budibase)
- Invalid format for field "${columnName}": "${columnData}". Datetime fields with ignoreTimezones must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SS".(Budibase/budibase)
- Project package dependency index is invalid.(Budibase/budibase)
- Link confirmation is invalid or has expired(Budibase/budibase)
- workspaceId is required(Budibase/budibase)
- Unsupported AI config type: ${model.configType}(Budibase/budibase)
- Project import could not remap datasource for query '${doc._id}'.(Budibase/budibase)
- Invalid format for field "${columnName}": "${columnData}". Datetime fields must be in ISO format, e.g. "YYYY-MM-DDTHH:MM:SSZ".(Budibase/budibase)
- Teams integration appId must be a valid UUID(Budibase/budibase)
- ${text || fallbackMessage}(Budibase/budibase)
- Too many redirects(ruby/ruby)
- Error getting license: ${message}(Budibase/budibase)
- HTTP Error {status}: {reason}(yt-dlp/yt-dlp)
- Agent log detail not found(Budibase/budibase)
- Project import failed while saving '${failedId}'.(Budibase/budibase)
- Grouping by fields of type "${targetSchema.type}" is not supported(Budibase/budibase)
- No such file(ytdl-org/youtube-dl)
- Failed to fetch import data - ${message}(Budibase/budibase)
- Project import could not remap external table '${entity._id}'.(Budibase/budibase)
…and 312 more across the corpus — use search.
Honest provenance: generated on 2026-08-29 from AI-assisted analysis of the linked records. See how records are made.