blocked
A blocked request is one where the target page could not be captured because access was stopped on purpose, either by RenderScreenshot or by the site itself. Unlike a timeout, the same request fails the same way every time, so a retry does not help. This page is part of the error code reference.
| HTTP status | 422 Unprocessable Entity |
error.type |
target_error |
error.code |
blocked in the API specification. Today: screenshot_failed with a message that starts with blocked_url: |
| Retry? | No. Change the URL. |
| Credits used | 0 |
Example response
When RenderScreenshot refuses the target, the response looks like this today:
{ "error": { "type": "target_error", "code": "screenshot_failed", "message": "blocked_url: host localhost is blocked", "retryable": true }, "request_id": "req_9f8e7d6c5b4a3210" }
The retryable flag is true for every render failure. For a blocked_url: message, ignore it: the next attempt is refused too.
Cause 1: RenderScreenshot refused the URL
The browser runs in RenderScreenshot's cloud, not on your network. To keep it from reaching private systems, every target is checked before the page loads, and again for every address the page connects to while it loads. The text after blocked_url: names the rule:
Message after blocked_url: |
What triggered it |
|---|---|
host localhost is blocked |
localhost, or a name ending in .localhost, .local, .internal, .intranet, .lan or .home.arpa, or metadata.google.internal |
private address 10.0.0.5 |
An IP literal in a private, loopback, link-local, carrier-grade NAT, multicast or reserved range, IPv4 or IPv6 |
host resolves to private address ... |
A public-looking hostname whose DNS answer is a private address |
navigation resolved to private address ... |
The page, or a redirect, landed on a private address |
a resource resolved to private address ... |
An image, script or frame on the page came from a private address |
port 8080 is not allowed |
Any port other than 80 and 443 |
embedded credentials are not allowed |
A URL of the form https://user:pass@host/ |
Fixes:
- Local development. Put the page behind a public URL: a preview deployment, a staging host or a tunnel with a public HTTPS address.
- Internal dashboards and reports. Render the HTML yourself and send it in the
htmlparameter. The renderer never fetches your network. See content input. - A non-standard port. Serve the page on 443 behind a reverse proxy.
- Basic auth in the URL. Remove the credentials from the URL and send them in
network.auth. See authenticated pages. - A private asset on a public page, such as an image from an internal CDN. Serve that asset publicly, or block its URL pattern with
block.requestsso the rest of the page can render.
Cause 2: The site blocked the renderer
Bot protection, a web application firewall or a geo-block can stop a headless browser. In that case the page usually still loads, so you get HTTP 200 and an image of a challenge or "access denied" page rather than an error. The API specification reserves the code blocked for this case.
Fixes:
- If you own the site, allow the renderer in your firewall or bot-management rules.
- If you do not, check whether the site allows automated access before you go further.
- If the site owner gives you a bypass header or cookie for automated visits, send it with
network.headersornetwork.cookiesin a POST request.
The bot protection and CAPTCHA guide covers this case in depth.
Handle it in code
Both listings below stop on a refusal instead of retrying, and keep the request ID for support. Tested on 2026-10-15 with Node 22.18 and with the Ruby gem 1.0.0 on Ruby 4.0, against a local stub that returns the response above.
Node.js
const target = 'http://localhost:3000/dashboard'; const res = await fetch('https://api.renderscreenshot.com/v1/screenshot', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: target, preset: 'og_card' }), }); if (!res.ok) { const { error, request_id: requestId } = await res.json(); const refused = error.code === 'blocked' || error.message.startsWith('blocked_url:'); if (res.status === 422 && refused) { // Permanent for this URL: do not retry, change the target instead. console.error(`Refused ${target}: ${error.message} (request ${requestId})`); process.exit(2); } throw new Error(`${res.status} ${error.code}: ${error.message}`); }
Ruby
require 'renderscreenshot' client = RenderScreenshot.client(ENV.fetch('RS_API_KEY')) options = RenderScreenshot::TakeOptions.url('http://localhost:3000/dashboard').preset('og_card') begin image = client.take(options) File.binwrite('screenshot.png', image) rescue RenderScreenshot::Error => e refused = e.code == 'blocked' || e.message.start_with?('blocked_url:') raise unless e.http_status == 422 && refused # Permanent for this URL: do not retry, change the target instead. warn "Refused: #{e.message} (request #{e.request_id})" exit 2 end
Both listings accept either the blocked code or the blocked_url: prefix, so they keep working when the dedicated code arrives.
Last verified 2026-10-15 against the renderer's URL checks.