invalid_url
The API returns invalid_url when the url value cannot be parsed as an absolute http:// or https:// URL. The API does not open a browser. Correct the value and send the request again. The same text will fail on every retry.
| Field | Value |
|---|---|
| HTTP status | 400 Bad Request |
error.type |
validation_error |
error.code |
invalid_url |
error.param |
url (batch: urls) |
retryable |
false |
| Credit used | No |
See the error code reference for the other codes.
Example response
{ "error": { "type": "validation_error", "code": "invalid_url", "message": "The URL 'example.com/pricing' is not a valid HTTP(S) URL", "param": "url", "retryable": false, "docs": "https://docs.renderscreenshot.com/errors/invalid_url" }, "request_id": "req_5d0e93b7a41c2f86" }
In a batch request, the message gives the position of the bad entry: Invalid URL at position 3: '...'. The whole batch is rejected, so remove or correct that entry and send the batch again.
What passes and what fails
We ran 23 inputs through the same check that the API uses (a strict RFC 3986 parser, then a test for the http or https scheme):
| Input | Result | Note |
|---|---|---|
https://example.com |
passes | |
HTTPS://EXAMPLE.COM |
passes | Scheme and host are not case-sensitive |
https://example.com/#/route |
passes | Fragments are fine |
https://example.com/%C3%BCber |
passes | Encoded non-ASCII path |
https://xn--exmple-cua.com |
passes | Punycode host |
example.com, www.example.com/page |
invalid_url | No scheme |
//example.com |
invalid_url | Protocol-relative |
ftp://example.com, javascript:alert(1) |
invalid_url | Wrong scheme |
https://example.com/a b |
invalid_url | Raw space |
" https://example.com", "https://example.com\n" |
invalid_url | Whitespace or newline around the value |
https://exämple.com, https://example.com/über |
invalid_url | Raw non-ASCII characters |
https://example.com/a[1] |
invalid_url | Square brackets, the pipe character and ^ must be percent-encoded |
Three inputs pass this check but still fail later, because the renderer refuses them for security reasons: https://example.com:8080/ (only ports 80 and 443), https://user:[email protected]/ (credentials in the URL) and private addresses such as https://[::1]/. Those come back as a target error, not invalid_url. The security guide lists the rules, and blocked covers the error.
Common causes in real code
- The scheme is missing. Users type
example.com. Addhttps://before you send. - The value has a newline. Lines read from a CSV or text file keep
\nunless you strip them. - The URL was encoded twice. In a GET request,
url=https%253A%252F%252F...decodes tohttps%3A%2F%2F..., which has no scheme. Encode the target URL once. - The URL comes straight from a browser address bar. Browsers show
überandexämple.com, but they send the encoded form. Copy the encoded form, or normalize it as shown below.
In a GET request, also encode & inside the target URL. Otherwise the API sees a shorter url plus extra parameters. That request does not return invalid_url. It captures the wrong page.
Normalize user input before you send it
This function accepts what people paste and returns a URL that passes the check, or a clear reason why it cannot:
// normalize-url.mjs (Node 22): turn user input into a URL the API accepts, or explain why not. export function normalizeTargetUrl(input) { let text = String(input ?? '').trim(); if (!text) throw new Error('empty URL'); if (/%3A%2F%2F/i.test(text)) text = decodeURIComponent(text); // pasted already encoded if (!/^[a-z][a-z0-9+.-]*:/i.test(text)) text = `https://${text.replace(/^\/\//, '')}`; // no scheme const url = new URL(text); // throws TypeError on input that cannot be a URL if (url.protocol !== 'http:' && url.protocol !== 'https:') { throw new Error(`unsupported scheme ${url.protocol}`); } if (url.username || url.password) throw new Error('credentials in the URL are not allowed'); if (url.port && url.port !== '80' && url.port !== '443') { throw new Error(`port ${url.port}: only ports 80 and 443 are reachable`); } // WHATWG leaves | ^ [ ] unencoded in the path and query; stricter parsers reject them. const enc = (s) => s.replace(/[|^[\]]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase()); return url.origin + enc(url.pathname + url.search) + url.hash; // punycode host, encoded path }
For example, https://exämple.com/über uns becomes https://xn--exmple-cua.com/%C3%BCber%20uns. Every URL the function returned in our test also passed the API's check. Without the last step, https://example.com/a|b would still fail.
Handle it in a batch job
When URLs come from a file or a database, invalid_url is a data problem, not an outage. With the Ruby SDK, collect the bad rows and continue:
require 'renderscreenshot' client = RenderScreenshot.client(ENV.fetch('RS_API_KEY')) rejected = [] File.readlines(ARGV.fetch(0), chomp: true).each_with_index do |url, line| image = client.take(RenderScreenshot::TakeOptions.url(url).preset('og_card')) File.binwrite("shot-#{line + 1}.png", image) rescue RenderScreenshot::Error => e raise unless e.code == 'invalid_url' # anything else is not a data problem rejected << [line + 1, url, e.message] end rejected.each { |line, url, message| warn "line #{line}: #{url.inspect}: #{message}" } exit(rejected.empty? ? 0 : 1)
Tested on 2026-10-15. The input table and the normalizer output were checked with Ruby 4.0.7's URI parser, which is the parser the API uses. The normalizer ran on Node 22.18. The Ruby job ran with the renderscreenshot gem 1.0.0 against a local mock that returns the API's error envelope.
Related
- missing_required: no
urlat all. - navigation_failed: the URL is valid, but the browser cannot reach it.
- URL options:
url,htmlandmarkdowninputs.