screenshots
capture viewport or full-page screenshots of any web page. screenshots are returned as plain public urls. when a screenshot is delivered, the request uses the 5-credit screenshot engine base before the proxy multiplier.
two behaviors to design around:
- requesting a screenshot always routes the scrape through browser rendering — this adds latency compared to an http scrape.
- in rare cases a screenshot can’t be captured. when that happens, the rest of your requested
outputs are delivered as normal and the
screenshotfield is simply left out — so always check forscreenshotrather than assuming it’s there. a request for only a screenshot fails with an error instead — see when no screenshot can be delivered.
screenshot types
| type | description |
|---|---|
viewport | captures what’s visible in the browser viewport |
full_page | captures the entire page, handling long pages adaptively |
pass the type inside the extract.screenshot object when calling POST /api/scrape or POST /api/scrape/async.
viewport screenshot
the simplest option. captures exactly what a user would see in the browser window without scrolling.
the default viewport is 1280×720 (desktop); it’s 390×844 in mobile device mode,
and you can override it with a custom viewport.
curl
curl -X POST https://api.crawlbrulee.com/api/scrape \
-H "Authorization: Bearer $CRAWLBRULEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "viewport"
}
}
}'response (condensed):
{
"screenshot": {
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/abc123/screenshot.webp",
"type": "viewport",
"properties": {
"file_name": "screenshot.webp",
"mime": "image/webp",
"width": 1280,
"height": 720,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
}
}
}full-page screenshot
captures the entire page in a single image, handling long pages adaptively.
curl
curl -X POST https://api.crawlbrulee.com/api/scrape \
-H "Authorization: Bearer $CRAWLBRULEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "full_page"
}
}
}'response (condensed):
{
"screenshot": {
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/def456/screenshot.webp",
"type": "full_page",
"properties": {
"file_name": "screenshot.webp",
"mime": "image/webp",
"width": 1280,
"height": 4200,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
}
}
}screenshots come back as WebP. when the finished image is more than 16,383 device pixels on either
side — width or height — we encode it as jpeg (image/jpeg) instead, since 16,383 is WebP’s
per-side maximum. an image sitting exactly at 16,383 still encodes as WebP. screenshots are never
returned as png.
slicing full-page screenshots
combine type: "full_page" with a slice action in actions_after to also get the page split into
horizontal tiles — useful when you need to feed a long screenshot to something with a smaller
per-image size limit (e.g. an image model). most legitimate pages are well under 10,000px tall, so
in practice slicing produces only a handful of tiles.
{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "full_page",
"actions_after": [{ "type": "slice", "height": 800 }]
}
}
}the response includes a slices array alongside the full screenshot:
{
"screenshot": {
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/ghi789/screenshot.webp",
"type": "full_page",
"properties": {
"file_name": "screenshot.webp",
"mime": "image/webp",
"width": 1280,
"height": 3200,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
},
"slices": [
{
"row_nr": 0,
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/ghi789/screenshot_slice_0.webp",
"type": "slice",
"properties": {
"file_name": "screenshot_slice_0.webp",
"mime": "image/webp",
"width": 1280,
"height": 800,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
}
},
{
"row_nr": 1,
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/ghi789/screenshot_slice_1.webp",
"type": "slice",
"properties": {
"file_name": "screenshot_slice_1.webp",
"mime": "image/webp",
"width": 1280,
"height": 800,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
}
}
]
}
}screenshot slicing adds a flat +1 credit when the requested slice variant is generated, regardless
of how many image tiles it contains. minimum slice height is 500px, and only one actions_after
entry is allowed. the number of slices produced is ceil(image_height / slice_height), capped at
20 per image. nothing is lost at the cap — the final slice carries the whole remaining height, so
it comes back taller than the tile height you asked for. see screenshot
slices for exactly how slicing interacts with
caching and credits.
pre-screenshot actions
actions_before lets you wait or scroll before taking the screenshot. you can chain up to 5 actions,
with a combined total wait of 20,000ms and a combined total scroll distance of 50,000px across all
actions — exceeding either limit returns a validation error.
{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "viewport",
"actions_before": [
{ "type": "wait", "ms": 2000 },
{ "type": "scroll", "pixels": 500 }
]
}
}
}| action | fields | description |
|---|---|---|
wait | ms (integer) | wait for the specified milliseconds before continuing |
scroll | pixels (integer) | scroll down by the specified pixels (use a negative value to scroll up) |
useful for:
- waiting for lazy-loaded content to appear
- scrolling to trigger animations or infinite-scroll loads
- giving cookie banners or overlays time to dismiss
actions_before entries with a non-zero wait or scroll disable caching for that request — each
call runs a fresh scrape. an actions_before array with only zero-value actions (or an empty
array) doesn’t affect caching.
custom viewport
override the default viewport dimensions to match the device or resolution you need. if you pass a
viewport object, both width and height are required.
{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "viewport",
"viewport": {
"width": 1920,
"height": 1080,
"device_scale_factor": 2
}
}
}
}| field | type | required | default | description |
|---|---|---|---|---|
width | integer | yes | — | viewport width in pixels. integer in the range 16–10000 |
height | integer | yes | — | viewport height in pixels. integer in the range 16–10000 |
device_scale_factor | number | no | 1 | pixel density multiplier, 1–3 (fractional values allowed). set to 2 for retina-quality |
values outside these ranges are rejected with a 400 at the api boundary. when viewport is omitted
entirely, the default comes from device_mode: 1280x720 for desktop, 390x844 for mobile.
how device_scale_factor is applied
an in-range device_scale_factor isn’t always used exactly as sent. the captured image is
width × device_scale_factor device pixels wide, and the browser can’t rasterise an image side
longer than 16,383 device pixels. when your combination would cross that, we snap the scale factor
down to the next standard device pixel ratio — 3, 2.5, 2, 1.75, 1.5, 1.25, 1 —
until the width fits, rather than hand you a silently cropped image. standard steps keep the
emulated ratio looking like real hardware.
so width: 1920 with device_scale_factor: 3 is captured as asked (5,760px wide), while
width: 10000 with device_scale_factor: 3 drops to 1.5 (15,000px wide, the widest standard step
that fits). the factor we actually captured with is echoed back in
properties.viewport.device_scale_factor — read it there when pixel density matters, instead of
assuming the requested value was used.
device mode
emulate desktop or mobile browsing. device mode controls the user agent string and the default viewport size.
| value | behavior |
|---|---|
"desktop" (default) | standard desktop user agent; default viewport 1280x720 |
"mobile" | mobile user agent; default viewport 390x844 |
{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "viewport",
"device_mode": "mobile"
}
}
}combine with a custom viewport to test specific mobile resolutions.
cleanup
by default, we remove ads, cookie banners, and popups before capturing the screenshot.
{
"url": "https://example.com",
"extract": {
"screenshot": {
"type": "viewport",
"cleanup": {
"ads_and_popups": true
}
}
}
}set ads_and_popups to false to capture the page as-is, overlays and all. the default is true.
request parameters
full reference for the extract.screenshot object.
| parameter | type | required | default | description |
|---|---|---|---|---|
type | string | yes | — | "viewport" or "full_page" |
viewport | object | no | 1280x720 (desktop) / 390x844 (mobile) | custom viewport dimensions — width and height both required if set |
device_mode | string | no | "desktop" | "desktop" or "mobile" |
cleanup | object | no | { ads_and_popups: true } | page cleanup before capture |
actions_before | array | no | [] | up to 5 pre-screenshot actions (wait, scroll); total wait ≤ 20,000ms, total scroll ≤ 50,000px |
actions_after | array | no | [] | up to 1 post-screenshot action (slice, minimum height 500px, at most 20 slices — the last one absorbs any remaining height) |
response schema
the screenshot object returned in the scrape response. in the rare case a requested screenshot
can’t be captured, this field is simply left out and the rest of your requested outputs are returned
as normal — unless the screenshot was the only thing you asked for, in which case the request fails
instead (see when no screenshot can be delivered).
| field | type | description |
|---|---|---|
url | string | public url to the screenshot image |
type | string | "viewport" or "full_page" |
properties.file_name | string | generated filename (e.g., screenshot.webp) |
properties.mime | string | image/webp, or image/jpeg when the image is over 16,383px on either side |
properties.width | number | image width in pixels |
properties.height | number | image height in pixels |
properties.viewport | object | viewport used: { width, height, device_scale_factor } |
slices | array | present when a slice action was requested via actions_after |
slices[].row_nr | number | zero-based index of the slice |
slices[].url | string | public url to the slice image |
slices[].type | string | always "slice" |
slices[].properties | object | same shape as the top-level properties |
warnings
a response can include a top-level warnings array of stable string codes. for screenshots, that’s
screenshot_truncated: the page was taller than the maximum capture height (15,000px), so the image
— and any slices — stop at that point instead of covering the full page. warnings are stored with the
result, so a cache hit reports the same cap the original capture did.
screenshots aren’t the only thing that can be capped — see limits and truncation for the full list of warning codes and the caps behind them.
{
"screenshot": {
"url": "https://cdn.crawlbrulee.com/scrape_assets/media/images/jkl012/screenshot.webp",
"type": "full_page",
"properties": {
"file_name": "screenshot.webp",
"mime": "image/webp",
"width": 1280,
"height": 15000,
"viewport": {
"width": 1280,
"height": 720,
"device_scale_factor": 1
}
}
},
"warnings": ["screenshot_truncated"]
}retention
screenshot urls remain valid for 60 days after the scrape. download the image before then if you need to keep it longer.
when no screenshot can be delivered
in rare cases a screenshot can’t be delivered. what happens depends on what else you requested:
- the request also asks for other outputs (markdown, links, …) — the scrape succeeds: every
other requested field is delivered as normal, the
screenshotfield is left out, and no error is raised. check forscreenshotinstead of assuming it’s there. - the request asks only for a screenshot — instead of returning an empty
200, the request fails:- the content type can’t be screenshotted (json, plain text, markdown, xml) →
422with error nameunsupported_screenshot_output. - the page is screenshottable but the capture failed →
500.
- the content type can’t be screenshotted (json, plain text, markdown, xml) →
in both failure cases you’re not billed — the failure is detected before any credits are consumed.
these semantics apply to sync scrapes (fresh and cached alike) and to async results fetched from
GET /api/scrape/result/{job_id}. see errors for the error format.
combining with other extraction options
you can request screenshots alongside any other extraction option in a single call.
{
"url": "https://example.com",
"extract": {
"markdown": true,
"links": true,
"screenshot": {
"type": "full_page"
}
}
}a delivered screenshot costs 5 credits on basic proxy and 25 on advanced. generating the
requested slice variant adds 1 flat credit after the multiplier. if an optional screenshot is not
delivered, the request is billed at the engine that delivered the base result. see credits &
pricing for the complete formula.