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_code | Status | Meaning |
|---|---|---|
invalid_parameter | 400 | A parameter is missing, malformed or out of range. The message names it and says what is accepted. |
host_not_resolved | 400 | The target host has no DNS record. Check the spelling, or that the site is online. |
private_address | 400 | The target (or a proxy, storage endpoint or webhook) points to a private or reserved address. Only public sites can be rendered. |
unauthorized | 401 | The API key is missing or wrong. Send it as 'Authorization: Bearer <key>', X-API-Key or access_key. |
insufficient_credits | 402 | The account has no credits left (or fewer than the request needs). Top up at /pricing. |
signature_invalid | 403 | The signed URL's signature or public key does not match. Sign the path plus the sorted query. |
not_found | 404 | No such job, monitor or file (jobs and hosted results expire). |
payload_too_large | 413 | An input (html, markdown, css, js, a template) or a downloaded file is larger than allowed. |
selector_not_found | 422 | 'selector' matched nothing visible. Set error_on_selector_not_found=false to capture the viewport instead. |
click_selector_not_found | 422 | 'click' matched nothing clickable. Set error_on_click_selector_not_found=false to continue anyway. |
hover_selector_not_found | 422 | 'hover' matched nothing. Set error_on_hover_selector_not_found=false to continue anyway. |
wait_for_selector_timeout | 422 | 'wait_for_selector' (or 'wait_to_leave') did not reach the expected state in time. |
content_contains_string | 422 | The page contains a string from fail_if_content_contains (see matched_string). Not charged. |
content_missing_string | 422 | The page lacks a string from fail_if_content_missing (see missing_string). Not charged. |
request_failed | 422 | A request matching fail_if_request_failed failed or answered 400 or above (see failed_request). Not charged. |
host_returned_error | 422 | The page answered HTTP 400 or above and fail_on_status is on. Not charged. |
navigation_failed | 422 | The page could not be loaded (connection refused, TLS error, too many redirects...). |
script_error | 422 | Your 'js' raised an exception in the page. |
not_a_page | 422 | The URL is a file download (for example a PDF), not a web page. Use /v1/markdown to read it. |
storage_upload_failed | 422 | Uploading the result to your S3-compatible bucket failed; the provider's message is included. |
timeout | 422 | The page did not load or finish rendering in time. Retry, raise 'timeout', or use wait_until=domcontentloaded. |
page_crashed | 422 | The page used too much memory and the browser tab crashed. Try a smaller viewport, scale or full_page_max_height. |
concurrency_limit | 429 | Too many renders in flight for this account (or a bulk/crawl job is already running). Retry shortly. |
rate_limited | 429 | A per-IP or per-day limit was reached. Retry later or use an API key. |
render_failed | 500 | An unexpected error while rendering. Retry; if it persists, contact support. |
server_busy | 503 | All browsers are busy. Retry in a few seconds. |
Status codes
| Status | Meaning |
|---|---|
400 | A parameter is invalid, or the host can't be reached or isn't allowed (private networks are blocked). |
401 | Missing or invalid API key. |
402 | Out of credits. Top up; failed renders were refunded. |
403 | A signed URL with a wrong signature or public key, or used on an endpoint it doesn't cover. |
413 | The HTML or Markdown body is too large. |
422 | The page couldn't be loaded, a selector wasn't found, a script failed, or a fail_… rule matched. |
429 | Too many renders at once for your account, or a keyless daily limit. Retry shortly. |
500 | An unexpected rendering error. Retry once; tell us if it persists. |
Response headers
X-Credits-Remaining | Credits left after this request. |
X-Render-Time-Ms | Server-side render time. |
X-Target-Status | The HTTP status the page itself answered with. A 404 page still renders unless fail_on_status is set. |
X-Shotlane-Blocked | Only 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-Truncated | A full-page capture was cut at 32,000 device pixels, and where. |
X-Cache | HIT (free), MISS or BYPASS. |
X-Shotlane-Cache-Url | A shareable link to the cached file, when caching is on. |
X-RateLimit-Limit / X-RateLimit-Remaining | Renders your account may run at once, and how many slots are free. |
X-Shotlane-External-Identifier | Your 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/accountand the MCPcreditstool.
Check your usage
GET /v1/usage (free) returns {"credits", "used_30d", "used_today", "concurrency": {"limit", "in_flight", "remaining"}}.
Limits
| Renders at the same time | 1 per account on the free tier, 3 once you've bought any pack. More return 429; queue or retry. |
| Render time | 90 seconds per render; navigation timeout 5–60 s. |
| Request bodies | html up to 2 MB, markdown up to 1 MB. |
| Full pages | Cut at 32,000 device pixels tall. |
| Bulk and crawl | 500 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.