ErrLookup › Background articles › "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format
"Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format
"Invalid URL" errors happen when a library parses a URL before using it and the string fails: a missing scheme, unencoded whitespace, an unexpected path shape, or a host:port pasted into the wrong field. This family covers the parse-time and format-time URL validators across dozens of open-source tools — from JavaScript's new URL() throwing TypeError, to Ruby's URI::InvalidURIError, to regex guards that demand https://github.com/owner/repo or /stats/matches/mapstatsid/:id/:slug exactly — and explains what each validator actually checks and how to fix the string.
Distilled from 94 documented records across 39 repositories.
Background
This family sits at the input-validation boundary of libraries that accept a URL from a user, a config file, or another program. Before making any network request, the library parses the string — with the WHATWG URL constructor in JavaScript (mastra, worldmonitor, 9router, danbooru's upload helper), Ruby's URI.parse (sure, grav via parse_url, october via RouterHelper), Go's url.Parse (siyuan), Rust's reqwest::Url (zeroclaw), or Python's urlparse (chroma) — or applies a purpose-built regex that encodes the only URL shape the feature can handle. The error fires when parsing fails outright, when the parsed result is missing a required component (scheme, host, owner/repo, numeric ID), or when the value violates a deliberate policy like https-only or whitespace-free.
Three distinct layers of strictness live under one message. The weakest is pure parseability: does the string survive the URL constructor at all? Relative paths like /pro or /callback fail here because new URL() requires an absolute URL, and control characters, unencoded spaces, and truncated pastes fail in every language. The middle tier is structural: the URL parses but lacks a required part — no scheme ("localhost:20128" is famously ambiguous because browsers would parse localhost as the scheme), no host after https://, a GitHub URL with fewer than owner/repo segments, a Hugging Face URL that ends at the repo root with no filename. The strongest tier is policy: the URL is perfectly valid but the wrong kind. danbooru only accepts http(s), forem's Parler tag requires an exact https://www.parler.io/audio/...mp3 shape, onetimesecret rejects http:// and data: URIs for tenant branding, and zeroclaw refuses any interior whitespace before even checking the scheme.
From the caller's side, the confusion comes from the gap between 'valid URL' as humans use it and what the specific validator demands. A developer pastes www.example.com/image.png or a git@github.com:owner/repo.git SSH remote — both meaningful, both wrong for these APIs. Some errors are also deliberate fail-closed security controls, not mere format checks: zeroclaw rejects whitespace because unencoded spaces can smuggle or corrupt headers; worldmonitor's returnUrl and callbackUrl checks are open-redirect defenses; the career-ops installer restricts plugins to pinned GitHub HTTPS URLs for supply-chain auditing. Reading the message as 'this library wants a narrower URL format than what I passed' is the key to fixing it.
How the check is implemented shapes what you see. WHATWG parsers (used across the JavaScript records) throw on unparseable input but accept surprising things — kiro:// passes because it has a scheme; localhost:PORT is misparsed rather than rejected. Ruby's URI.parse accepts scheme-less strings silently (host ends up nil), so libraries that care must add an is_a?(URI::HTTP) check on the class, which is why sure's Anthropic setting demands an explicit http(s) scheme. Regex validators fail fast with the exact offending URL in the message, and a few records (chroma's host="localhost:8000/api" parsed as scheme 'localhost', angular's http://host:8080:9090 double-port) show how a valid-looking string lands in an unexpected branch of a lenient parser. The same phrase 'invalid URL' therefore covers everything from an unencoded space to a missing trailing slash after a numeric ID.
Common causes
- Missing URL scheme. Passing "www.example.com/x", "localhost:20128", or "example.com/cb" where an absolute URL is required. new URL(), URI::HTTP checks, and scheme-anchored regexes all demand an explicit http:// or https:// prefix; scheme-less input either throws or parses into the wrong class.
- URL missing a required structural component. The URL parses but lacks the part the feature needs: a GitHub URL without owner/repo (mastra, siyuan), a tweet URL without /status/<numeric-id> (OpenCLI), an Hugging Face repo URL with no file path (sglang), or https:// with an empty host (onetimesecret). Validators count path segments and require the tail to exist.
- Unencoded whitespace or control characters. Spaces, tabs, and newlines pasted from terminals or produced by string concatenation break parsing or are rejected outright — zeroclaw rejects any interior whitespace before scheme checks, and whitespace-bearing strings fail new URL(), URI.parse, and reqwest::Url in the records. Leading/trailing whitespace sometimes gets trimmed first; interior whitespace never passes.
- Wrong URL shape for a strict format validator. Regex-based guards accept exactly one pattern: career-ops requires https://github.com/<owner>/<repo> with no trailing slash, /tree/... segments, or SSH syntax; forem requires an exact parler.io .mp3 URL; OpenCLI requires /stats/matches/mapstatsid/:id/:slug. Valid URLs of any other shape are refused.
- Relative path passed where an absolute URL is required. Return and callback targets like "/pro" or "/callback" cannot be parsed by new URL() without a base, so worldmonitor and 9router reject them. Build the value from location.origin (or the configured site URL) plus the route instead.
- Non-HTTP or disallowed scheme. ftp://, file://, data:, anthropic://, ssh remotes (git@github.com:...), and plain http:// where https-only is policy are all rejected in the records even when structurally valid. Some checks are explicit security controls (open-redirect and supply-chain guards) and should not be routed around.
- host:port or path pasted into a field that takes only a host. Chroma rejects host="localhost:8000/api/v2" because urlparse reads 'localhost' as the scheme — the port belongs in the port argument and paths in dedicated options. Uppy's s3mini similarly requires the endpoint as a full https://host[:port][/base-path] URL, not a bare hostname.
- Truncated or mangled paste and interpolation artifacts. Truncated copy/paste, query strings that scanners stop at (forem's charset halts at ? and uppercase), strings like 'Not Available' from free-text fields, or 'null'/'undefined' interpolated into template strings all produce unparseable values. Log the exact string (JSON.stringify or equivalent) to see hidden characters.
What usually fixes it
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
- [object Object]
Go deeper
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- Server returned an invalid response (${submissionUrl}) (onetimesecret/onetimesecret)
- refusing non-GitHub/unsafe repo URL: ${arg} (expected https://github.com/<owner>/<repo>) (santifer/career-ops)
- Necessary parameters not found in Uplynk URL (yt-dlp/yt-dlp)
- URL cannot contain whitespace (zeroclaw-labs/zeroclaw)
- Invalid GitHub URL format (mastra-ai/mastra)
- Invalid institution URL for <%= class_name %> account #{provider_account.id}: #{url} (we-promise/sure)
- INVALID_URL: Invalid URL: ${urlStr} (angular/angular)
- invalid download URL: %s (siyuan-note/siyuan)
- Anthropic Base URL must be an http(s) URL. (we-promise/sure)
- Invalid Parler URL (forem/forem)
- Invalid URL. Unrecognized protocol - {parsed.scheme}. (chroma-core/chroma)
- Invalid returnUrl: must be a valid absolute URL (koala73/worldmonitor)
- Invalid callback URL format (decolua/9router)
- Invalid URL (danbooru/danbooru)
- Could not extract tweet ID from URL: ${value} (jackwener/OpenCLI)
- Factory linked work item URL is invalid. (mastra-ai/mastra)
- series resolution requires a /stats/matches/mapstatsid/:id/:slug URL (jackwener/OpenCLI)
- Twitter reply completion returned a malformed status url. (jackwener/OpenCLI)
- LinkedIn connect blocked: invalid_connect_link (jackwener/OpenCLI)
- Malformed GPM URL: {$package_file} (getgrav/grav)
…and 74 more across the corpus — use search.
Honest provenance: generated on 2026-08-31 from AI-assisted analysis of the linked records. See how records are made.