Browserless

Screenshot, PDF, HTML, and structured scrape — four fixed browser-automation actions behind a closed field policy

View as Markdown

Fronts four single-shot actions on Browserless’s REST API (https://production-sfo.browserless.io) — each one page load, one browser session, one response:

POST /browserless/screenshot → https://production-sfo.browserless.io/screenshot
POST /browserless/pdf → https://production-sfo.browserless.io/pdf
POST /browserless/content → https://production-sfo.browserless.io/content
POST /browserless/scrape → https://production-sfo.browserless.io/scrape

Browserless is a full browser-automation platform, not just these four actions — BrowserQL, two async job APIs (Agent Run and Crawl), and a session-oriented WebSocket product (BaaS) all sit behind the same account token every NativePort tenant shares. Only the four actions above are single-shot, single-session, and carry no id a foreign tenant could ever replay, so only those are mounted.

BQL, Agent Run, Crawl, /unblock, /function, and BaaS are not exposed. Any other Browserless path draws the gateway’s own 404. A leaked Agent Run/Crawl job id or a BaaS WebSocket URL would be reachable by any NativePort tenant, not just other Browserless customers — Browserless’s own per-token checks protect its customer base from each other, not one NativePort tenant from another.

Unlike every other route on this gateway, /browserless doesn’t just relay your request — it runs a closed field policy first: a fixed allow-set of query parameters and JSON body fields, everything else rejected before Browserless is ever touched.

Credential handling

  • token is injected as the first query parameter, forced regardless of anything you send — including a decoded-name evasion attempt (t%6Fken=...).
  • Authorization is dropped before the request goes upstream, even when you also pass a token query value yourself.
  • timeout is forced to 30000 (30 seconds) regardless of what you send. Every body-level timeout duplicate Browserless documents — gotoOptions.timeout, waitForSelector.timeout, waitForFunction.timeout, waitForEvent.timeout, and the top-level waitForTimeout — is stripped entirely (not merely overwritten), so no combination of sub-timeouts can push a session past that one bound.

Field policy

Every JSON body field is explicitly admitted or rejected — there’s no passthrough for anything the list below doesn’t name.

Admitted on all four routes: url, html, gotoOptions, waitForSelector, waitForFunction, waitForEvent, rejectResourceTypes, rejectRequestPattern, bestAttempt.

One extra field per route: options on /screenshot and /pdf; elements on /scrape. /content admits only the common set above.

Denied outright, at both the query and body level — a 400 before the request reaches Browserless: stealth (which also triggers Browserless’s own server-side CAPTCHA solving), blockAds, blockAdsInclude, blockConsentModals, proxy, proxyCountry, externalProxyServer, launch, emulationOs, and profile (a saved, token-scoped session — reachable by every tenant sharing the one Browserless token this gateway injects). Any other query parameter or body field the policy doesn’t name is rejected the same way: default-deny, not default-allow. The query string admits nothing beyond the forced token/timeout pair.

A rejected call answers 400 with {"error": "Rejected: <reason>."} and is never forwarded — Browserless never sees it, and it’s never billed.

Screenshot

POST /browserless/screenshot — send url or html (not both). options carries Puppeteer-style screenshot settings (type: png/jpeg/webp, fullPage, quality, clip, and so on).

$curl -X POST "$GATEWAY/browserless/screenshot" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "options": {"fullPage": true, "type": "png"}}' \
> --output screenshot.png

Response: binary image bytes, image/png by default (image/jpeg or image/webp when options.type requests it), relayed byte-for-byte.

PDF

POST /browserless/pdf — send url or html (not both). options mirrors Puppeteer’s PDF settings (format, printBackground, displayHeaderFooter, pageRanges, and so on).

$curl -X POST "$GATEWAY/browserless/pdf" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "options": {"format": "A4", "printBackground": true}}' \
> --output output.pdf

Response: binary application/pdf, relayed byte-for-byte.

Rendered HTML

POST /browserless/content — send url or html (not both); returns the fully rendered page, JavaScript included.

$curl -X POST "$GATEWAY/browserless/content" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com"}'

Response: text/html, the rendered document.

Structured scrape

POST /browserless/scrapeelements (required alongside url or html) lists the CSS selectors to capture: [{"selector": "h1"}, ...].

$curl -X POST "$GATEWAY/browserless/scrape" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "elements": [{"selector": "h1"}]}'

Response: application/json, one result set per selector (text, html, attributes, layout box).

Waiting, navigation, and error handling

The common fields above cover Browserless’s shared request configuration: waitForSelector/waitForFunction/waitForEvent/gotoOptions.timeout (their own .timeout sub-fields stripped, per the forced 30-second bound above) gate when the action runs; gotoOptions (minus .timeout) controls navigation (waitUntil: load|domcontentloaded|networkidle0|networkidle2); rejectResourceTypes/rejectRequestPattern block unwanted requests; bestAttempt: true returns whatever page state exists instead of failing when a wait times out. Field-level detail for all of these lives in Browserless’s own docs.

Billing

Billed a flat $0.004 per accepted, forwarded call, charged synchronously before the request reaches Browserless — Browserless bills browser-session time regardless of outcome, so the charge lands the moment the gateway commits to forwarding and applies the same whether the call succeeds, Browserless answers a non-2xx, or the upstream request times out or fails outright. A call the field policy rejects (see above) is never charged.

There’s no idempotency key on this route: a retry after a failed or timed-out call opens its own new Browserless session and is billed as its own, independent attempt.

Field-level request and response detail for the four admitted actions lives in Browserless’s REST API docs; the gateway modifies neither beyond the field policy described above.