Errors and limits

Errors are JSON with a message that says what to do next. Failed renders are never charged.

Error format

HTTP/1.1 422 Unprocessable Content
{"error": "Selector not found or not visible: #pricing"}

Error codes

Every error also carries a stable error_code to branch on and a documentation_url pointing here:

{"error": "Selector not found or not visible: #pricing", "error_code": "selector_not_found",
 "documentation_url": "https://shotlane.dev/docs/errors#selector_not_found"}
error_codeStatusMeaning
invalid_parameter400A parameter is missing, malformed or out of range. The message names it and says what is accepted.
host_not_resolved400The target host has no DNS record. Check the spelling, or that the site is online.
private_address400The target (or a proxy, storage endpoint or webhook) points to a private or reserved address. Only public sites can be rendered.
unauthorized401The API key is missing or wrong. Send it as 'Authorization: Bearer <key>', X-API-Key or access_key.
insufficient_credits402The account has no credits left (or fewer than the request needs). Top up at /pricing.
signature_invalid403The signed URL's signature or public key does not match. Sign the path plus the sorted query.
not_found404No such job, monitor or file (jobs and hosted results expire).
payload_too_large413An input (html, markdown, css, js, a template) or a downloaded file is larger than allowed.
selector_not_found422'selector' matched nothing visible. Set error_on_selector_not_found=false to capture the viewport instead.
click_selector_not_found422'click' matched nothing clickable. Set error_on_click_selector_not_found=false to continue anyway.
hover_selector_not_found422'hover' matched nothing. Set error_on_hover_selector_not_found=false to continue anyway.
wait_for_selector_timeout422'wait_for_selector' (or 'wait_to_leave') did not reach the expected state in time.
content_contains_string422The page contains a string from fail_if_content_contains (see matched_string). Not charged.
content_missing_string422The page lacks a string from fail_if_content_missing (see missing_string). Not charged.
request_failed422A request matching fail_if_request_failed failed or answered 400 or above (see failed_request). Not charged.
host_returned_error422The page answered HTTP 400 or above and fail_on_status is on. Not charged.
script_error422Your 'js' raised an exception in the page.
not_a_page422The URL is a file download (for example a PDF), not a web page. Use /v1/markdown to read it.
storage_upload_failed422Uploading the result to your S3-compatible bucket failed; the provider's message is included.
timeout422The page did not load or finish rendering in time. Retry, raise 'timeout', or use wait_until=domcontentloaded.
page_crashed422The page used too much memory and the browser tab crashed. Try a smaller viewport, scale or full_page_max_height.
concurrency_limit429Too many renders in flight for this account (or a bulk/crawl job is already running). Retry shortly.
rate_limited429A per-IP or per-day limit was reached. Retry later or use an API key.
render_failed500An unexpected error while rendering. Retry; if it persists, contact support.
server_busy503All browsers are busy. Retry in a few seconds.

Status codes

StatusMeaning
400A parameter is invalid, or the host can't be reached or isn't allowed (private networks are blocked).
401Missing or invalid API key.
402Out of credits. Top up; failed renders were refunded.
403A signed URL with a wrong signature or public key, or used on an endpoint it doesn't cover.
413The HTML or Markdown body is too large.
422The page couldn't be loaded, a selector wasn't found, a script failed, or a fail_… rule matched.
429Too many renders at once for your account, or a keyless daily limit. Retry shortly.
500An unexpected rendering error. Retry once; tell us if it persists.

Response headers

X-Credits-RemainingCredits left after this request.
X-Render-Time-MsServer-side render time.
X-Target-StatusThe HTTP status the page itself answered with. A 404 page still renders unless fail_on_status is set.
X-Shotlane-BlockedOnly when the site answered with a bot-protection page (e.g. Cloudflare, Akamai, DataDome) instead of its content. Not charged and never cached.
X-Shotlane-TruncatedA full-page capture was cut at 32,000 device pixels, and where.
X-CacheHIT (free), MISS or BYPASS.
X-Shotlane-Cache-UrlA shareable link to the cached file, when caching is on.
X-RateLimit-Limit / X-RateLimit-RemainingRenders your account may run at once, and how many slots are free.
X-Shotlane-External-IdentifierYour external_identifier, echoed.

What's never charged

  • Failed renders and timeouts.
  • Bot-protection pages instead of the site.
  • Cache hits.
  • Requests a fail_… rule stopped.
  • GET /v1/account and the MCP credits tool.

Check your usage

GET /v1/usage (free) returns {"credits", "used_30d", "used_today", "concurrency": {"limit", "in_flight", "remaining"}}.

Limits

Renders at the same time1 per account on the free tier, 3 once you've bought any pack. More return 429; queue or retry.
Render time90 seconds per render; navigation timeout 5–60 s.
Request bodieshtml up to 2 MB, markdown up to 1 MB.
Full pagesCut at 32,000 device pixels tall.
Bulk and crawl500 URLs or pages per job; one running job per account.
Keyless/md/ and /shot/: 20 a day per IP; free web tools: 10 a day.

Network

Every page renders in a fresh, sandboxed browser that can only reach the public internet; private and cloud-metadata addresses are blocked at the network level, including through redirects. Requests to your site come from 2.28.130.34 and 2a01:4f8:c016:ba46::/64, if you want to allowlist them.