Browserless
Screenshot, PDF, HTML, and structured scrape — four fixed browser-automation actions behind a closed field policy
Fronts four single-shot actions on Browserless’s REST API (https://production-sfo.browserless.io) — each one page load, one browser session, one response:
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
tokenis injected as the first query parameter, forced regardless of anything you send — including a decoded-name evasion attempt (t%6Fken=...).Authorizationis dropped before the request goes upstream, even when you also pass atokenquery value yourself.timeoutis forced to30000(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-levelwaitForTimeout— 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).
Response: binary image bytes, image/png by default (image/jpeg or image/webp when options.type requests it), relayed byte-for-byte.
POST /browserless/pdf — send url or html (not both). options mirrors Puppeteer’s PDF settings (format, printBackground, displayHeaderFooter, pageRanges, and so on).
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.
Response: text/html, the rendered document.
Structured scrape
POST /browserless/scrape — elements (required alongside url or html) lists the CSS selectors to capture: [{"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.