Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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: true captures the complete rendered document.
  • clip captures a coordinate rectangle when you know the exact x, y, width and height.
  • selector limits the image to one CSS-selected element, useful for a chart, card or invoice.

Choose image appearance

  • type selects the output image format supported by the endpoint.
  • omitBackground can preserve transparency where the page and chosen format support it.
  • quality applies 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 deviceScaleFactor when 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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
The SQL Programming Language: .
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.