How to Use Browserless to Screenshot a Website
Capture a website with Browserless’s Screenshot REST API using cURL, Python, or Node.js. Learn its key options, troubleshoot common failures, and see a simpler API alternative.
To screenshot a website with Browserless, send a JSON POST request to its Screenshot API and save the binary response as an image file. You need a Browserless API token. The examples below use the documented US West endpoint; use the host configured for your own account or fleet if it differs.
1. Get a Browserless API token
Create or sign in to your Browserless account and retrieve an API token from its dashboard. Replace YOUR_API_TOKEN_HERE in the examples with your own token. Treat it as a secret: do not commit it to a public repository, include it in a shared screenshot, or expose it in browser-side code.
The request route is POST /screenshot. The token goes in the token query parameter, and the JSON body supplies the target URL and capture options. The response is image bytes, not JSON. Browserless’s Screenshot API documentation describes the route, inputs, and options; its quickstart covers account setup. The hostname below is the documented example and may not be the right host for every fleet.
2. Capture a page with cURL
curl -X POST \
"https://production-sfo.browserless.io/screenshot?token=YOUR_API_TOKEN_HERE" \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/","options":{"fullPage":true,"type":"png"}}' \
--output screenshot.png
Run this in a shell with cURL installed. The command writes the response directly to screenshot.png. fullPage: true requests a capture of the full page rather than just the current viewport, and type: "png" selects PNG output. For a quick viewport-only capture, omit fullPage or set it to false.
Check that the file exists and is a valid image before using it. An HTTP error response may also be written to the output path, depending on the cURL options and response. For easier diagnosis, add -i to display response headers, or use -fS so cURL reports HTTP errors instead of silently treating the response as a successful download.
3. Capture a page with Python
import requests
endpoint = "https://production-sfo.browserless.io/screenshot"
params = {"token": "YOUR_API_TOKEN_HERE"}
payload = {
"url": "https://example.com/",
"options": {"fullPage": True, "type": "png"},
}
response = requests.post(
endpoint,
params=params,
json=payload,
timeout=90,
)
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
Install the dependency with python -m pip install requests. The json= argument serializes the body and sets the JSON content type. raise_for_status() surfaces HTTP failures before the response is saved. The timeout prevents the client from waiting indefinitely; adjust it to suit your calling environment and the time your pages need to load.
4. Capture a page with Node.js
const endpoint = new URL("https://production-sfo.browserless.io/screenshot");
endpoint.searchParams.set("token", "YOUR_API_TOKEN_HERE");
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://example.com/",
options: { fullPage: true, type: "png" },
}),
});
if (!response.ok) {
throw new Error(`Browserless returned HTTP ${response.status}: ${await response.text()}`);
}
const image = new Uint8Array(await response.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) => writeFile("screenshot.png", image));
This uses the built-in fetch available in current Node.js releases. Save binary response data with arrayBuffer(); calling response.json() is incorrect for the REST screenshot response. Keep the token in an environment variable in a real application rather than embedding it in source code.
5. Choose screenshot options
The REST Screenshot API accepts a URL or supplied HTML and returns image data. The documented formats include PNG, JPEG, and WebP. Its options follow Puppeteer-style screenshot settings. Use only the settings needed for the capture; large full-page images can consume more memory and take longer to produce.
| Need | Setting or input | What it does |
|---|---|---|
| Choose an output format | options.type: "png", "jpeg", or "webp" |
Selects an image format documented by the API. Match the filename extension to the selected format. |
| Capture beyond the viewport | options.fullPage: true |
Captures the full page rather than only the visible viewport. |
| Control lossy image quality | options.quality |
Use for JPEG or WebP when supported by the endpoint. Quality is not meaningful for PNG. Consult the API documentation for accepted values and format-specific behavior. |
| Capture a fixed region | options.clip |
Defines a rectangular clip using screenshot geometry fields. Use this when you need a known region of the page rather than a DOM element. |
| Capture one element | Top-level selector |
Targets an element by CSS selector. This is distinct from a fixed rectangular options.clip. |
| Control viewport and scale | Viewport and device-scale screenshot settings under options |
Set the browser dimensions and output pixel scale when layout or retina-sized output matters. Confirm the exact accepted option names in the current docs. |
| Provide a page instead of navigating to a URL | HTML input supported by the API | Useful for rendering markup you supply. Consult the current request schema for the precise field and behavior. |
Example element request body:
{
"url": "https://example.com/",
"selector": "main article",
"options": { "type": "png" }
}
Example fixed-region request body:
{
"url": "https://example.com/",
"options": {
"type": "png",
"clip": { "x": 0, "y": 0, "width": 1200, "height": 800 }
}
}
Selectors depend on the page’s actual DOM. If the selector matches nothing, is hidden, or appears only after client-side rendering, the resulting capture may fail or omit the intended content. Refer to the current option reference for the accepted schema and constraints.
6. Handle lazy-loaded and blocked pages
Lazy-loaded content
Some pages load images or sections only after scrolling them into view. If the screenshot misses content farther down the page, use Browserless’s scrollPage: true guidance to scroll before capture, together with fullPage: true when you need the full document. Scrolling can trigger additional network requests, so allow for extra capture time. See the Screenshot API guidance.
CAPTCHA, blank page, or access denied
A CAPTCHA, blank result, or access-denied page can mean the destination is blocking automated browsers. Browserless documents an /unblock route for anti-bot cases and mentions residential proxies for best results, but neither guarantees a successful capture. Use them only when permitted by the destination’s terms and your authorization. Do not treat a CAPTCHA or access-denied screenshot as a successful capture of the intended page. Troubleshooting guidance is in the official screenshot documentation.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Unauthorized or token error | The token is missing, invalid, expired, or belongs to a different account or host. | Check the token in your account dashboard, ensure it is URL-encoded by your HTTP client, and use the endpoint assigned to your fleet. |
| Request rejected or malformed | The method, content type, or JSON structure is incorrect. | Use POST, set Content-Type: application/json, and send valid JSON with the URL and options. |
| Downloaded file is HTML or JSON, not an image | The server returned an error body and the client saved it as though it were image bytes. | Inspect the HTTP status and response body; call raise_for_status() in Python or check response.ok in Node. In cURL, try -fS and inspect headers. |
| Only the top of the page appears | Full-page mode is absent or false. | Set options.fullPage to true. For lazy content, also enable the documented scroll behavior. |
| Images or lower sections are missing | Content loads on scroll or after asynchronous page work. | Use scrollPage: true where appropriate and allow the page enough time to load. Confirm the content is present in a normal browser session. |
| Element capture is empty | The CSS selector does not match, the element is hidden, or it has not rendered yet. | Inspect the page’s DOM, use a selector that matches a visible element, and verify the selector syntax. |
| Screenshot shows a CAPTCHA or denial | The destination is blocking automated access. | Confirm you have permission to access it. Browserless documents /unblock and residential proxies as possible approaches; results are not guaranteed. |
| Client times out | The page is slow, keeps making requests, or full-page capture is expensive. | Set an appropriate client timeout, reduce capture scope if possible, and avoid launching many simultaneous captures without capacity planning. |
8. Performance, reliability, and cost
- Capture size: Full-page screenshots and high device scale produce more pixels and larger responses. Capture a viewport, selector, or clip when that is all you need.
- Latency: Navigation, JavaScript rendering, lazy loading, and target-site response time affect how long a request takes. Use client timeouts that account for the page, and inspect failures instead of retrying every request immediately.
- Reliability: A successful HTTP response does not by itself prove that the page rendered the intended content. Check the response status and validate important outputs, especially when the destination may show bot checks or access-denied pages.
- Retries: Retry transient network or server failures with bounded backoff. Avoid aggressive retries for invalid tokens, malformed requests, or destination blocks; those conditions need correction rather than repetition.
- Secrets: Keep tokens on the server side or in a secret manager. Do not expose them in client-side JavaScript or public examples.
- Cost: Browserless usage and pricing depend on the account and plan. Check your dashboard and current Browserless plan details before estimating production cost; the cited technical documentation does not establish a price for an individual capture.
9. Optional: use BrowserQL instead
If you already use Browserless BrowserQL, its documented screenshot flow is a GraphQL mutation that navigates and returns screenshot bytes encoded as base64. That differs from the REST route above, which returns image bytes directly. Decode the base64 result before saving it as an image. BrowserQL is an alternative API path, not a required step for a basic website screenshot. See the BrowserQL screenshot example.
Or skip the browser setup
ScreenshotNeo takes a website screenshot with one GET request. Its API removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers say what happened. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Every feature is on every plan.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
const image = new Uint8Array(await res.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) => writeFile("shot.webp", image));
See the ScreenshotNeo API documentation for request options. Sign up free for 1,000 screenshots a month with no card.
FAQ
Does Browserless return JSON for a screenshot?
No. The REST Screenshot API returns image data, so save the response bytes to a file. BrowserQL’s documented example uses base64 data instead.
Can I capture HTML that I already have?
The Screenshot API supports a supplied HTML input as well as a webpage URL. Follow the current REST request schema for the exact HTML field and any related options.
Why does a full-page capture miss some images?
Those images may load only when scrolled into view. Use the documented scroll behavior before capture and allow the page’s additional requests to complete.
Is the example Browserless hostname correct for every account?
No. It is the host in Browserless’s documentation example. A private or regional fleet may require a different hostname.
Will Browserless always get past a CAPTCHA?
No. A bot check can indicate that the destination blocks automation. The documented anti-bot route and proxy guidance do not guarantee access.


