Screenshot
Capture the whole rendered page as a full-page PNG — top to bottom, not just the fold — or size, crop and clean it up first: any viewport, retina, one element, JPEG or WebP, dark mode, and the ads and cookie banners out of the way. The image comes back Base64-encoded, ready to store or decode to a file, with a link to it in the response headers.
It uses no AI, and costs 1 credit per call — whichever options you use. See Credits.
Capture a screenshot
Body parameters
- Name
url- Type
- string
- Required
- Required
- Description
- The absolute URL of the page to process. Rendered with headless Chrome, so JavaScript runs and redirects are followed. It must not include a username or password (
https://user:pass@example.com).
- Name
screenshot_options- Type
- object
- Description
- How to take the screenshot: size, format, what to wait for and what to hide. Every key is optional and included in the credit — see Options. Leave it out for a full-page PNG.
- Name
page_options- Type
- object
- Description
- Wait for the page, and remove ads, cookie banners or your own elements from it before it is read — gone from this result, not merely hidden. See Page options.
- Name
residential- Type
- boolean
- Description
- Fetch the page from a residential exit — an address on a home broadband line rather than one in a datacentre. Reach for it when a site serves you less than it serves a browser, or nothing at all. Defaults to
false. Adds 25 credits per page fetch on top of what the operation costs, and its results are kept separate from the ordinary ones.
- Name
report_to- Type
- string
- Description
- Webhook URL — an
httporhttpsaddress URLpipe POSTs the result to when it's ready. Optional: without it we deliver to your project's default endpoint if it has one, and otherwise send no webhook at all — the result still waits for you at GET /result/:token. A value we cannot deliver to returns422. Ignored on async=truerequest. Deliveries can be signed so your endpoint can verify they came from us.
- Name
sync- Type
- boolean
- Description
- Process the request synchronously, returning the result inline in the response. Defaults to
false(async: return a token now, and either receive the result at a webhook or fetch it with GET /result/:token). See Async & sync modes for the full contract.
- Name
max_age- Type
- string | integer
- Description
- How fresh a cached result must be to be accepted. Either an integer number of seconds (
3600) or a duration string of the form"<number> <unit>"— unitss/min/h/d/w(e.g."2 hours","3 days","30m"). Defaults to7 days, clamped to a max of30 days;0always bypasses the cache. See Caching for all accepted units.
- Name
labels- Type
- object
- Description
- Your own keys to find and account for this request by — a client, a project, a campaign:
{"client": "acme"}. Returned with the result, in the webhook and in theX-Labelsheader, and your dashboard filters history and totals credits by them. Up to 16 keys; string values. See Labels.
Response
Content type text/plain — the body is the image, Base64-encoded: a PNG unless you asked for another format. Decode it to bytes, or prefix it with data:image/png;base64, (or image/jpeg, image/webp) to use it directly in an <img> tag.
Screenshots are full-page unless you ask otherwise: the whole scrollable document, not just what fits above the fold. The page is scrolled through first so lazy-loaded images are actually in the picture.
The viewport is 1350 × 797 — a desktop browser window — unless you set one, so two captures of a page line up and can be compared. The height follows the page, up to a ceiling of 16,384 pixels; a page taller than that is captured down to it.
The response also carries an X-Result-Url header: a link to the same image that needs no API key, so it goes straight into an <img> tag, an email or a social card. It stays valid for the 30 days the result is kept — see Response headers.
curl -X POST https://urlpipe.dev/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "screenshot_options": {"format":"webp"}, "page_options": {"block_cookie_banners":true}}'import requests
res = requests.post(
"https://urlpipe.dev/screenshot",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"url": "https://example.com", "screenshot_options": {"format": "webp"}, "page_options": {"block_cookie_banners": True}},
)const res = await fetch("https://urlpipe.dev/screenshot", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://example.com", screenshot_options: {"format":"webp"}, page_options: {"block_cookie_banners":true} }),
})$ch = curl_init("https://urlpipe.dev/screenshot");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["url" => "https://example.com", "screenshot_options" => ["format" => "webp"], "page_options" => ["block_cookie_banners" => true]]),
]);
$response = curl_exec($ch);UklGRlq4AABXRUJQVlA4WAoAAAAgAAAARQUAhAUA…
(Base64-encoded WebP, truncated)Options
The keys of screenshot_options. Each one you leave out is its default, and a request that spells out a default is the same request as one that omits it — it is served the same stored result. A value out of range, or a key not listed here, is refused with invalid_options before anything is fetched or spent.
{
"url": "https://example.com/pricing",
"screenshot_options": {
"viewport_width": 390,
"device_scale_factor": 2,
"format": "webp",
"hide_selectors": ["#chat-widget"]
},
"page_options": {
"block_cookie_banners": true
}
}What the page is made to wait for or leave out — ads, cookie banners, an element that loads late — is set with page_options, which every endpoint that loads the page takes.
Size and shape
- Name
full_page- Type
- boolean
- Description
- Capture the whole page top to bottom (the default), or set
falsefor the viewport only. A viewport capture of a URL with a#fragmentis scrolled to that anchor, the way a browser opens it.
- Name
full_page_max_height- Type
- integer
- Description
- The tallest the image may be, in CSS pixels, for a full page or an element:
100–16384(the default). A page taller than this is captured down to it.
- Name
viewport_width- Type
- integer
- Description
- The browser window's viewport width,
320–1920. Default1350. Use390to see a site's phone layout.
- Name
viewport_height- Type
- integer
- Description
240–1080. Default797. Decides what the fold is, and the size of anything a page sizes to the screen.
- Name
device_scale_factor- Type
- integer
- Description
- Pixel density,
1–3.2renders a retina image at twice the width and height, with the high-resolution images a retina display is sent.
- Name
selector- Type
- string
- Description
- A CSS selector: capture that one element instead of the page — a pricing table, a chart, a hero. The first match is used; a selector that matches nothing is a failed analysis and costs nothing.
Format
- Name
format- Type
- string
- Description
png(default),jpegorwebp. WebP is usually the smallest by far for a long page.
- Name
quality- Type
- integer
- Description
- JPEG and WebP only,
1–100. Default80. A PNG is always lossless, so this has no effect on one.
- Name
omit_background- Type
- boolean
- Description
- Leave the background transparent where the page itself sets none. PNG and WebP only — a JPEG has no transparency.
Look
- Name
dark_mode- Type
- boolean
- Description
- Render with
prefers-color-scheme: dark, so a site that has a dark theme shows it.
- Name
hide_selectors- Type
- array
- Description
- CSS selectors of elements to hide in the image — a chat widget, a promo bar. Up to 50. They are hidden, not removed: to take them out of the page itself, use
page_options.remove_selectors.
- Name
styles- Type
- string
- Description
- CSS to add to the page before capturing, up to 20,000 characters.
Using the image URL
If what you want is a link rather than bytes — a preview in a page you're rendering, an image in a digest email — read it off the response instead of decoding anything. The URL carries its own signed, expiring credentials, so it works anywhere, with no key attached and nothing to proxy.
curl -sD - -o /dev/null -X POST https://urlpipe.dev/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "sync": true}' \
| grep -i '^x-result-url'res = requests.post(
"https://urlpipe.dev/screenshot",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"url": "https://example.com", "sync": True},
)
image_url = res.headers["X-Result-Url"]const res = await fetch("https://urlpipe.dev/screenshot", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://example.com", sync: true }),
})
document.querySelector("img").src = res.headers.get("X-Result-Url")$ch = curl_init("https://urlpipe.dev/screenshot");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"url" => "https://example.com",
"sync" => true,
]),
]);
$response = curl_exec($ch);
$headers = substr($response, 0, curl_getinfo($ch, CURLINFO_HEADER_SIZE));
preg_match('/^x-result-url: *(.+)$/mi', $headers, $matches);
$imageUrl = trim($matches[1]);An async request has no response headers to read, so the same link arrives in the webhook payload as result_url, beside the result itself — see Async mode.
Decoding the image
curl -s -X POST https://urlpipe.dev/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' \
| base64 --decode > screenshot.pngimport base64, requests
res = requests.post(
"https://urlpipe.dev/screenshot",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"url": "https://example.com"},
)
with open("screenshot.png", "wb") as f:
f.write(base64.b64decode(res.text))const res = await fetch("https://urlpipe.dev/screenshot", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://example.com" }),
})
const base64 = await res.text()
const dataUri = `data:image/png;base64,${base64}`$ch = curl_init("https://urlpipe.dev/screenshot");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_API_KEY",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["url" => "https://example.com"]),
]);
$base64 = curl_exec($ch);
file_put_contents("screenshot.png", base64_decode($base64));Responses
Whatever the status, the response carries metadata headers: the result token, whether it was served from cache and how old that result is, how long we took, what it cost in credits, and the allowance you have left.
200 OK422 Unprocessable Entity429 Too Many Requests504 Gateway Timeout401 UnauthorizedTry it live — no API key needed
Run this endpoint against any URL right in your browser.