Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Direct answer: Create a Cloudflare API token with Browser Rendering permission, then send a JSON POST request to https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/screenshot. Include either a url or html; Cloudflare renders the page, runs its JavaScript and returns image bytes. Add screenshotOptions, viewport and gotoOptions when you need full-page output, a particular element, a different format or a longer wait.
What you need before making a request
- A Cloudflare account and the target account ID.
- An API token with the Browser Rendering Write permission for REST requests.
- A client that can preserve a binary HTTP response, such as curl, Python or Node.js.
Cloudflare documents a second route for Workers: bind Browser Run to your Worker and call env.BROWSER.quickAction("screenshot", ...). That binding path does not require an API token.
Minimal REST screenshot
The smallest request supplies a URL. The endpoint processes HTML and JavaScript before taking the screenshot, rather than capturing the initial response.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/screenshot'
-H 'Authorization: Bearer <apiToken>'
-H 'Content-Type: application/json'
-d '{"url":"https://example.com"}'
--output screenshot.png
--output is essential: the successful response is image data, not JSON. Replace both placeholders with your account ID and token. Keep the token out of shell history, source control and browser-side JavaScript.
#1 Best Overall
Full-page capture and viewport control
Use fullPage: true when the image must include content below the initial viewport. The documented default viewport is 1,920 × 1,080 pixels; set it explicitly for repeatable output.
{
"url": "https://cloudflare.com/",
"screenshotOptions": {"fullPage": true},
"viewport": {"width": 1280, "height": 720},
"gotoOptions": {"waitUntil": "networkidle0", "timeout": 45000}
}
Send that object as the request body with the same headers shown above. networkidle0 waits for network activity to become quiet; a 45,000-millisecond navigation timeout prevents a page that never settles from consuming the request indefinitely.
Screenshot options that matter
Choose the page extent
fullPage: truecaptures the complete rendered document.clipcaptures a coordinate rectangle when you know the exact x, y, width and height.selectorlimits the image to one CSS-selected element, useful for a chart, card or invoice.
Choose image appearance
typeselects the output image format supported by the endpoint.omitBackgroundcan preserve transparency where the page and chosen format support it.qualityapplies to formats that support quality settings. It is incompatible with the default PNG format, so choose a supported JPEG or other format before setting quality.- Increase
deviceScaleFactorwhen a very large viewport looks soft; more device pixels increase output size and processing work.
Control readiness and page changes
gotoOptions controls navigation waits and timeout. Cloudflare also documents addScriptTag and addStyleTag for changing a page immediately before capture. Request and resource allowlists can restrict what the browser loads. The actionTimeout maximum documented in the API reference is 120,000 milliseconds.
Recommended Free Tools
Capture HTML instead of a public URL
url and html are alternatives; at least one is required. Use html when your application already has the markup and does not want Cloudflare to navigate to an external address. The same screenshot options and viewport settings can be added to the JSON body.
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/screenshot'
-H 'Authorization: Bearer <apiToken>'
-H 'Content-Type: application/json'
-d '{"html":"<html><body><h1>Invoice 42</h1></body></html>","screenshotOptions":{"type":"png"}}'
--output invoice.png
Authenticated pages: cookies, headers and Basic Auth
Cookies
Provide cookies through the documented browser request options when the page uses a session cookie. Treat cookie values like passwords and generate them per job where possible.
Rank #2
HTTP headers
Cloudflare documents setExtraHTTPHeaders for custom headers, including an application’s Authorization header. Do not put long-lived credentials in a URL because URLs are commonly logged.
HTTP Basic Authentication
For a site protected by Basic Auth, use the documented authenticate option rather than embedding credentials in the URL. If the site uses a login form, navigate to it and use the page’s interaction options or provide a session cookie obtained by your own authentication flow.
Python example with binary-safe error handling
import requests
account_id = "<accountId>"
api_token = "<apiToken>"
endpoint = f"https://api.cloudflare.com/client/v4/accounts/{account_id}/browser-rendering/screenshot"
payload = {
"url": "https://example.com",
"screenshotOptions": {"fullPage": True, "type": "png"},
"viewport": {"width": 1280, "height": 720},
"gotoOptions": {"waitUntil": "networkidle0", "timeout": 45000}
}
response = requests.post(
endpoint,
headers={"Authorization": f"Bearer {api_token}", "Content-Type": "application/json"},
json=payload,
timeout=150,
)
if not response.ok:
raise RuntimeError(f"Cloudflare returned {response.status_code}: {response.text}")
with open("screenshot.png", "wb") as image:
image.write(response.content)
The longer client timeout leaves room for the API’s navigation and action limits. Always inspect non-2xx responses as text; attempting to save an error JSON document as a PNG makes diagnosis harder.
Node.js example
const accountId = "<accountId>";
const apiToken = "<apiToken>";
const endpoint = `https://api.cloudflare.com/client/v4/accounts/${accountId}/browser-rendering/screenshot`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Authorization": `Bearer ${apiToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
url: "https://example.com",
screenshotOptions: { fullPage: true, type: "png" },
viewport: { width: 1280, height: 720 },
gotoOptions: { waitUntil: "networkidle0", timeout: 45000 }
})
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const image = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(fs => fs.writeFile("screenshot.png", image));
Workers Browser Run binding
When the capture belongs inside a Cloudflare Worker, configure a Browser Run binding and call it directly. This avoids putting a REST API token in the Worker request path.
export default {
async fetch(request, env) {
const result = await env.BROWSER.quickAction("screenshot", {
url: "https://example.com",
screenshotOptions: { fullPage: true, type: "png" },
viewport: { width: 1280, height: 720 }
});
return new Response(result, {
headers: { "Content-Type": "image/png" }
});
}
};
Use the REST API when an external service, CI job or backend owns the workflow. Use the binding when the capture is naturally part of a Worker deployment and you want Cloudflare-managed authentication between the Worker and Browser Run.
Rank #3
- Used Book in Good Condition
Rate limits, retries and reliable production jobs
For Workers Paid plans, Cloudflare increased the Browser Rendering REST API limit on March 4, 2026 to 10 requests per second (600 per minute). A burst above that can produce HTTP 429, which Cloudflare labels “Rate limit exceeded.” Retry 429 responses with exponential backoff and jitter, and cap the number of attempts so a queue cannot grow without bound.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →- Give every job an idempotency key in your own queue so a client timeout does not create untracked duplicate work.
- Record status code, elapsed time, target URL, viewport, format and whether the request used full-page mode.
- Use a realistic client timeout greater than the configured navigation timeout.
- Prefer a readiness condition that matches the page. Network idle can take a long time on analytics-heavy sites; a selector or bounded delay may be more predictable when supported by your page workflow.
- Keep output format and dimensions stable if screenshots are compared in tests. Large full-page images consume more memory and storage.
Troubleshooting
401 or 403 response
Check that the Bearer token is present, not expired and belongs to the account ID in the URL. Confirm the token has Browser Rendering Write permission. A Worker binding does not use this REST token.
400 response about the body
Supply exactly one of url or html, send valid JSON and include Content-Type: application/json. Validate that nested option names use the documented casing.
The image is blank or incomplete
The page may still be rendering when capture starts, or content may require scrolling. Try fullPage, a suitable waitUntil, a bounded timeout and a selector-based readiness step. Check that required requests are not excluded by an allowlist.
Quality is rejected
PNG does not accept the quality setting. Select a supported JPEG or other format first.
Rank #4
Large captures look blurry
Raise deviceScaleFactor and test a smaller CSS viewport. Higher scale produces a sharper image but increases bytes and processing cost.
429 rate-limit errors
Throttle concurrency, honor backoff, and spread scheduled jobs. On Workers Paid plans, the documented ceiling is 10 requests per second.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo is the #1 alternative to try first when you want a screenshot API: it removes cookie banners, newsletter popups and chat widgets before capture, bills only clean shots, and its lowest paid plan is $5.
A single GET request returns the image:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for all options. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and each response identifies the page verdict and billing state in X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server so Claude, Cursor and other MCP clients can call 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. Create a free ScreenshotNeo account.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsFAQ
Can I capture a single element rather than the whole page?
Yes. Set screenshotOptions.selector to a CSS selector, or use clip for fixed coordinates.
Does the REST endpoint return JSON?
Successful requests return image bytes. Error responses should be read as JSON or text for diagnosis.
Best Value
What is the largest allowed action timeout?
The API reference documents a maximum actionTimeout of 120,000 milliseconds.
Frequently Asked Questions
Can I capture a single element rather than the whole page?
Yes. Set screenshotOptions.selector to a CSS selector, or use clip for fixed coordinates.
Does the REST endpoint return JSON?
Successful requests return image bytes. Error responses should be read as JSON or text for diagnosis.
What is the largest allowed action timeout?
The API reference documents a maximum actionTimeout of 120,000 milliseconds.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

