API referenceScreenshot API documentation
One endpoint turns a URL or your own HTML into a PNG, JPEG, WebP or PDF. Cookie banners, popups, chat widgets, ads and trackers are handled by default, and only clean, freshly rendered shots count toward your plan. The machine-readable version of this page is openapi.json.
Quick start
Create a key in the dashboard, then:
curl "https://api.screenshotneo.com/v1/shot?access_key=YOUR_KEY&url=https://stripe.com&format=webp" -o stripe.webp
Add full_page=true for the whole page, device=iphone_15 for a phone, format=pdf for a PDF, or response_type=json for a file URL plus the page’s title, description and Open Graph data.
Authentication
Send your key as access_key in the query or JSON body, as an X-Access-Key header, or as Authorization: Bearer YOUR_KEY. Keys are shown once and stored only as a hash; revoke and replace them from the dashboard. Each key also has a signing secret for signed links and webhook signatures.
Endpoints
| Method and path | What it does |
|---|
GET /v1/shot | Capture a URL; parameters in the query string. |
POST /v1/shot | The same with a JSON (or form) body, needed for html, and handy for headers, cookies and scripts. |
GET /v1/jobs/{id} | State and result of an async job. |
POST /v1/bulk | Queue up to 100 captures at once. |
GET /v1/bulk/{id} | Progress of a batch, with every job’s result. |
GET /v1/usage | Plan, quota, use and reset date for the key’s account. |
GET /v1/files/{name} | A stored result from a JSON or async response (no key needed; the name can’t be guessed). |
POST /mcp | MCP server for AI agents (Streamable HTTP). |
| Parameter | Values | What it does |
|---|
url | string | The page to capture. https:// is added when missing. Private and internal addresses are refused, including through redirects. |
html | string | Raw HTML to render instead of a URL (POST it in a JSON body). Good for Open Graph images and templates. Up to 2 MB. |
Output
| Parameter | Values | What it does |
|---|
format | png · jpeg · webp · pdfdefault png | Output format. jpg is accepted for jpeg. |
qualityalso image_quality | 1–100default 80 | JPEG and WebP quality. |
image_widthalso thumb_width, thumbnail_width | 16–3840 | Resize the result to this width (thumbnails). With image_height too, the result is cropped to fill both. |
image_heightalso thumb_height | 16–4320 | Resize the result to this height. |
omit_backgroundalso transparent | true · falsedefault false | Transparent background (PNG and WebP) where the page sets none. |
attachment_namealso download | string | Send the file as a download with this name. |
Viewport
| Parameter | Values | What it does |
|---|
devicealso viewport_device | desktop · laptop · full_hd · macbook_air · tablet · ipad_air · ipad_pro · mobile · iphone_15 · iphone_15_pro_max · pixel_8 · galaxy_s24 | A preset that sets width, height, pixel ratio, mobile mode and touch in one go. |
widthalso viewport_width | 320–3840default 1280 | Browser width in CSS pixels. |
heightalso viewport_height | 240–4320default 800 | Browser height in CSS pixels. |
scalealso device_scale_factor | 1–3default 1 | Device pixel ratio. 2 gives a Retina image twice the size. |
mobilealso viewport_mobile | true · falsedefault false | Mobile mode: the page’s meta viewport applies and a mobile browser is reported. |
touchalso viewport_has_touch | true · falsedefault false | Report a touch screen. |
landscapealso viewport_landscape | true · falsedefault false | Swap width and height. |
Full page and area
| Parameter | Values | What it does |
|---|
full_page | true · falsedefault false | Capture the whole scrollable page, not just the first screen. |
full_page_scroll | true · falsedefault same as full_page | Scroll down first so lazy images and sections load. |
full_page_max_heightalso max_height | 500–16000default 16000 | Stop a full-page capture at this height (useful for endless feeds). |
selector | string | Capture only the first element matching this CSS selector. |
clip | string | Capture a rectangle: x,y,width,height in CSS pixels. clip_x, clip_y, clip_width and clip_height work too. |
Waiting
| Parameter | Values | What it does |
|---|
wait_until | auto · load · domcontentloaded · networkidle0 · networkidle2default auto | When the page counts as loaded. auto = the load event plus up to 3 seconds for the network to settle. |
wait_for_selectoralso wait_for | string | Wait until an element matching this selector exists (up to 20 seconds). If it never appears, the call fails and isn’t billed. |
delay | 0–20default 0 | Extra seconds to wait before the capture. |
timeout | 5–90default 40 | Give up after this many seconds. A timeout isn’t billed. |
Cleaning the page
| Parameter | Values | What it does |
|---|
block_cookie_bannersalso no_cookie_banners, hide_cookie_banners | true · falsedefault true | Accept or remove cookie and consent dialogs (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and 50 more). |
block_chats | true · falsedefault true | Remove chat widgets (Intercom, Drift, Crisp, Zendesk, HubSpot, Tawk and others). |
block_adsalso no_ads | true · falsedefault true | Block ad networks. |
block_trackersalso no_tracking | true · falsedefault true | Block analytics and tracking scripts. |
block_requests | array | Block URLs matching these patterns, like *.example.com/widget*. Repeat the parameter or separate with commas. |
block_resources | stylesheet · image · media · font · script · texttrack · xhr · fetch · eventsource · websocket · manifest · other | Block whole kinds of requests. |
Your CSS, JS and clicks
| Parameter | Values | What it does |
|---|
hide_selectorsalso hide_selector | array | Hide elements matching these selectors. Repeat the parameter for several. |
stylesalso css | string | CSS to add to the page before the capture. |
scriptsalso js | string | JavaScript to run in the page before the capture; await works. If it throws, the call fails and isn’t billed. |
click | string | Click the first element matching this selector, then wait for the page to settle. |
Identity and location
| Parameter | Values | What it does |
|---|
dark_mode | true · false | true or false sets prefers-color-scheme; leave it out for the site’s default. |
reduced_motion | true · falsedefault false | Set prefers-reduced-motion: reduce. |
media_typealso media | screen · print | Emulate screen or print CSS. |
user_agent | string | A user agent of your own. By default a current Chrome on Windows (or Android on mobile) is reported. |
accept_languagealso accept_lang | stringdefault en-US,en;q=0.9 | Accept-Language sent to the site. |
cookiesalso cookie | array | Cookies as name=value, optionally with ; Domain=… ; Path=…. Repeat for several. |
authorization | string | An Authorization header for the page’s own site, like Basic … or Bearer …. |
timezonealso time_zone, tz | string | IANA time zone, like Europe/Berlin. |
latitudealso geolocation_latitude | -90–90 | Geolocation latitude (with longitude). |
longitudealso geolocation_longitude | -180–180 | Geolocation longitude. |
PDF
| Parameter | Values | What it does |
|---|
pdf_paperalso pdf_paper_format | a3 · a4 · a5 · letter · legal · tabloiddefault a4 | Paper size for format=pdf. |
pdf_landscape | true · falsedefault false | Landscape pages. |
pdf_backgroundalso pdf_print_background | true · falsedefault true | Print background colours and images. |
pdf_margin | stringdefault 0.4in | Margin on every side, in px, mm, cm or in. |
pdf_page_ranges | string | Only these pages, like 1-3,5. |
Checks
| Parameter | Values | What it does |
|---|
fail_if_content_contains | array | Fail (not billed) if the page text contains this. Case-insensitive; repeat for several. |
fail_if_content_missing | array | Fail (not billed) if the page text doesn’t contain this. |
fallback | og | og: when a site blocks automated browsers, return its own share image instead of an error. |
allow_blocked | true · falsedefault false | Return the capture even when it shows a bot check (still not billed). |
Caching
| Parameter | Values | What it does |
|---|
cache | true · falsedefault false | Keep the result for 24 hours; the same request from your account within that time is served from the cache and isn’t billed. |
cache_ttlalso ttl | 0–2592000 | Cache for this many seconds instead (up to 30 days). Implies cache=true; 0 turns caching off. |
cache_key | string | Keep separate cached copies of the same request. |
freshalso force | true · falsedefault false | Render again even when a cached copy exists. |
Response and async
| Parameter | Values | What it does |
|---|
response_type | image · json · emptydefault image | image returns the file; json returns a file URL (kept 24 hours) with page metadata; empty returns only headers. |
async | true · falsedefault false | Queue the render and answer at once with a job id (202). Poll /v1/jobs/{id} or add webhook_url. |
webhook_urlalso webhook | string | We POST the result here when the job finishes, signed with your key’s signing secret. Implies async. |
external_idalso external_identifier | string | Your own id, returned in the job and the webhook. |
X-Page-Verdict | ok, og (share image fallback) or blocked (with allow_blocked=true). |
X-Billed | 1 when this shot counted toward your plan, 0 when it didn’t. |
X-Cache | HIT when served from your cache (never billed), MISS otherwise. |
X-Render-Ms | How long the render took, in milliseconds. |
X-Quota-Remaining | Clean shots left in this period. |
X-RateLimit-Limit / X-RateLimit-Remaining | Your requests per minute, and how many are left in the current minute. |
X-Page-Status | The HTTP status the captured page answered with. |
X-Request-Id | The id of this render in your request log. |
X-Ignored-Params | Parameters we didn’t recognise, so a typo doesn’t go unnoticed. |
JSON responses
With response_type=json you get a link to the file (kept for 24 hours, or your cache time if longer) and what we read from the page:
{
"id": "1843", "status": "done", "verdict": "ok", "billed": true, "cached": false, "render_ms": 1843,
"url": "https://github.com",
"image": { "url": "https://api.screenshotneo.com/v1/files/c5b1…fd.webp", "format": "webp", "width": 1280, "height": 800, "bytes": 41210, "expires_at": "…" },
"metadata": { "title": "GitHub · Build and ship software…", "description": "…", "language": "en",
"canonical": "https://github.com/", "favicon": "https://github.githubassets.com/favicons/favicon.svg",
"final_url": "https://github.com/", "status": 200,
"og": { "title": "…", "description": "…", "image": "https://…", "site_name": "GitHub", "type": "object" } }
}Errors
Errors are JSON with an error code and a plain message. Anything that isn’t a clean screenshot is never billed.
| Status | error | Meaning |
|---|
| 400 | bad_request | A parameter is missing or out of range. param names it. |
| 400 | url_not_allowed | Not http(s), or the address is private, local or unresolvable. |
| 401 | missing_or_invalid_key | No key, or a revoked one. |
| 402 | quota_exceeded | This period’s clean shots are used up. Upgrade, or wait for the reset date in the message. |
| 403 | signature_required / bad_signature | The key only takes signed requests, or the signature doesn’t match. |
| 403 | email_not_verified / account_disabled | Confirm your e-mail in the dashboard, or write to us. |
| 422 | bot_check | The site showed a bot check even after a retry. Not billed. |
| 422 | blank_page | The page rendered empty. Not billed. |
| 422 | selector_not_found / script_error / content_check_failed | Your selector, script or content check failed. Not billed. |
| 422 | url_not_allowed | The page redirected to a private address. Not billed. |
| 429 | rate_limited / concurrency_limit | Over your requests per minute, or too many renders at once. Retry-After says when. |
| 502 | navigation_failed / render_failed | The site couldn’t be loaded or rendered. Not billed. |
| 504 | timeout | The page didn’t finish within timeout. Not billed. |
Limits and billing
- Monthly quota: clean shots per period; the period starts on your subscription date (the calendar month on the free plan). We e-mail you at 80% and 100%.
- Requests per minute and renders at once depend on the plan; over them you get 429 with Retry-After. Use
async=true to queue big jobs instead. - Never billed: bot checks, blank pages, timeouts, failed loads, script or selector errors, content-check failures and cache hits.
- Page size: full pages stop at 16,000 px; a page that downloads more than 60 MB stops loading and is captured as it is.
Signed links
Put a screenshot straight into an <img> without exposing a usable key: sign the query string with your key’s signing secret (HMAC-SHA256, hex) and add it as signature. Change any parameter and the signature stops matching. In the dashboard you can make a key accept signed requests only.
const qs = new URLSearchParams({ access_key: KEY, url: 'https://example.com', format: 'webp' }).toString();
const signature = crypto.createHmac('sha256', SIGNING_SECRET).update(qs).digest('hex');
const src = `https://api.screenshotneo.com/v1/shot?${qs}&signature=${signature}`; // safe to put in an <img>Async and webhooks
Add async=true (or a webhook_url) and the call returns 202 with a job id at once. Poll /v1/jobs/{id}, or let us POST the result to your webhook. Deliveries retry after 2 s, 15 s and 60 s and carry the same X-ScreenshotNeo-Delivery id each time; X-ScreenshotNeo-Signature is t=timestamp,v1=HMAC-SHA256(secret, timestamp + "." + body).
// Express: check the X-ScreenshotNeo-Signature header before trusting a webhook
const [t, v1] = req.get('x-screenshotneo-signature').split(',').map((p) => p.split('=')[1]);
const want = crypto.createHmac('sha256', SIGNING_SECRET).update(`${t}.${rawBody}`).digest('hex');
if (want !== v1 || Date.now() / 1000 - Number(t) > 300) return res.sendStatus(400);
// X-ScreenshotNeo-Delivery is the same on every retry: store it and drop repeats.Bulk
curl -X POST https://api.screenshotneo.com/v1/bulk -H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" -d '{
"defaults": { "format": "webp", "image_width": 600 },
"webhook_url": "https://your.app/hooks/shots",
"requests": [ { "url": "https://stripe.com" }, { "url": "https://linear.app", "full_page": true } ]
}'Each request becomes a job in one batch; follow it at /v1/bulk/{batch_id}. Every job is billed like a single call.
MCP for AI agents
Point any MCP client at https://api.screenshotneo.com/mcp with your key as a Bearer token. Tools: take_screenshot, get_page_info and capture_pdf.
{ "mcpServers": { "screenshotneo": { "url": "https://api.screenshotneo.com/mcp", "headers": { "Authorization": "Bearer YOUR_KEY" } } } }Coming from another API
Most parameter names that other screenshot APIs use work here as they are: viewport_width, device_scale_factor, image_quality, thumbnail_width, css, js, wait_for, no_ads, no_cookie_banners, ttl, time_zone and more (listed under each parameter above). Change the host and the key, and check X-Ignored-Params for anything we don’t know yet.