docs · migrating from browserless
Migrating from Browserless
Most Browserless code works by changing the endpoint and the token. Not all of it: Flatbrowser has no built-in proxy pool, no persistent profiles, no stealth mode and no Playwright wire protocol. The rule is simple — anything that would change where your traffic leaves from, who your browser appears to be, or what state it starts with is either honoured or refused with a 400 that names it. It is never silently dropped.
Status in the tables: supported works as on Browserless · ignored accepted, has no effect, reported back · rejected refused with a 4xx and a reason · native Flatbrowser’s own equivalent.
The swap
// before
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=BROWSERLESS_TOKEN',
});
// after
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY',
});- Your token goes in
?token=as before (orAuthorization: Bearer). Create one in the dashboard. - Playwright users: keep
chromium.connectOverCDP(). If you usedchromium.connect()against/chromium/playwright, switch toconnectOverCDP— see connect URLs. - Concurrency: Browserless queues extra connections; we answer 429 with
Retry-Afterunless you passqueueTimeout(up to 60 s), which makes the gateway wait for a free slot like Browserless does.
How you find out what was ignored
Parameters and body keys we accept but do not act on are listed, by name only, in the X-Flatbrowser-Ignored-Params response header — on the REST response, and on the WebSocket handshake response for CDP connections. Log it once during your migration.
curl -si -X POST 'https://gw.flatbrowser.com/screenshot?blockAds=true' \
-H 'Authorization: Bearer flat_live_YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"url":"https://example.com","options":{"type":"jpeg","quality":80}}' -o shot.jpg
# HTTP/1.1 200 OK
# Content-Type: image/jpeg
# X-Flatbrowser-Ignored-Params: blockAdsConnect URLs
| path | status | notes |
|---|---|---|
/ · /chromium · /chrome | supported | Creates a session; the browser is Chromium in every case. |
/chromium/playwright · /playwright/chromium (and the chrome forms) | supported | With connectOverCDP(). A chromium.connect() client (Playwright wire protocol) gets 400 playwright_protocol_not_supported instead of hanging. |
/devtools/browser/<id> | supported | Attach to an existing session; same as /session/<id>. |
/session/<id> | native | Attach to a session created with POST /v1/sessions or kept with keepalive. |
/chromium/stealth · /chrome/stealth | rejected | No stealth mode: standard headless Chromium only. |
/firefox · /webkit · /edge and their playwright forms | rejected | Chromium only. |
/devtools/page/<id> | rejected | Connect at browser level and pick the page there. |
HTTP discovery
| endpoint | status | notes |
|---|---|---|
GET /json/version (also under /chromium, /chrome) | supported | For Puppeteer’s browserURL (key in the Authorization header — Puppeteer drops the query string) and Playwright’s http form of connectOverCDP. |
/json/list · /json/new · /json/protocol | rejected | 404: sessions are created by connecting, not by HTTP. |
Query parameters
These apply to WebSocket connect URLs, POST /v1/sessions and the REST actions. A parameter that is not in this table is refused with 400 unsupported_param — far more often a typo of a meaningful parameter than a harmless extra.
| parameter | status | notes |
|---|---|---|
token · apiKey · api_key | supported | Or Authorization: Bearer. |
timeout | supported | Total session lifetime in ms, as on Browserless (capped at 12 h). On a REST action: the guard for the whole call, default 60 s, max 180 s. |
proxy=<url> · externalProxyServer=<url> · --proxy-server=<url> | supported | Your upstream proxy (http, https, socks4/4a/5/5h, credentials allowed). See proxies. |
proxy=residential · proxy=datacenter | rejected | 400 residential_proxy_not_available: there is no managed proxy pool. Bring your own proxy URL. |
proxyCountry · proxyLocaleMatch · proxyTimezone | supported | Only together with your own proxy: proxyCountry=de&proxyLocaleMatch=true sets a matching browser locale and timezone. Without a proxy URL they are rejected (400 residential_proxy_not_available) rather than egressing from our IP. |
proxySticky · proxyCity · proxyState · proxyPreset | ignored | With your own proxy (stickiness is your proxy vendor’s setting); without one: rejected as above. |
launch | supported | URL-encoded JSON. args pass the Chromium-flag allowlist, proxy works like proxy=, acceptInsecureCerts/ignoreHTTPSErrors work. Other keys (defaultViewport, slowMo, ignoreDefaultArgs, timeout, …) are ignored. |
--<chromium-flag>=… | supported | Forwarded if on the allowlist (window size, language, user agent and similar). A flag outside it is refused with a 400 that names it. Flags we already set (--no-sandbox, --disable-gpu, --headless, …) are ignored. |
profile · userDataDir · --user-data-dir | rejected | No persistent profiles: every session starts empty. Keep login state client-side (storageState, cookies). |
stealth · humanlike · solveCaptchas | rejected | Not offered, and not planned. A false value is ignored. |
headless=false · launch.devtools | rejected | Sessions always run headless. headless=true/new is ignored. |
record · replay · blockConsentModals | rejected | Not offered. |
sessionId | rejected | Attach with /session/<id> instead. |
blockAds · trackingId · ttl | ignored | Safe to drop: they change nothing about egress, identity or state. |
keepalive · idleTimeout · maxDuration · queueTimeout · locale · timezone · affinity | native | See sessions and proxies. |
REST endpoints
The Browserless-style paths take Browserless request bodies and answer like Browserless. The native /v1/* endpoints keep their own flat bodies (see the REST reference).
| endpoint | status | notes |
|---|---|---|
POST /content | supported | Returns text/html, like Browserless. |
POST /screenshot | supported | PNG or JPEG (options.type, quality, fullPage, clip, omitBackground) and selector. WebP and encoding: base64 are rejected. |
POST /pdf | supported | All common options: format, landscape, printBackground, scale, width/height, margin, header/footer templates, pageRanges, preferCSSPageSize. |
POST /scrape | supported | Each match returns text and html; Browserless’s attributes and box positions are not included. |
/chromium/<action> · /chrome/<action> | supported | Same as the unprefixed paths. |
POST /function · /download · /export · /performance · /unblock | rejected | 404. Use a CDP session for custom page logic. |
POST /session (Session API) | native | Use POST /v1/sessions; its connectUrl needs your token appended. |
/sessions · /pressure · /active | rejected | 404. Your active sessions: GET /v1/sessions. |
Request body keys on the Browserless-style paths
| key | status | notes |
|---|---|---|
url · html | supported | Exactly one of them. HTML documents up to 4 MB (body limit 5 MB). |
gotoOptions | supported | waitUntil (networkidle0/2 map to networkidle), timeout, referer. |
waitForSelector · waitForTimeout | supported | Up to 30 s. |
viewport · userAgent · emulateMediaType · setJavaScriptEnabled | supported | Viewport up to 7680 × 4320. |
cookies · setExtraHTTPHeaders · authenticate | supported | Up to 50 cookies and 50 headers. |
rejectResourceTypes · bestAttempt | supported | |
waitForFunction · waitForEvent · addScriptTag · addStyleTag · requestInterceptors · rejectRequestPattern | rejected | They would run your code inside our shared gateway. Use a CDP session for page logic. |
| anything else | ignored | Reported in X-Flatbrowser-Ignored-Params. |
CDP extensions and session behaviour
| feature | status | notes |
|---|---|---|
Browserless.* CDP methods (reconnect, liveURL, saveProfile, …) | rejected | Chromium answers “method not found”. |
| reconnecting after a disconnect | native | Use keepalive or a REST-created session and attach to /session/<id>. See sessions. |
| multiple pages, contexts and clients per session | supported | All count as one session. |
| uploads | supported | Playwright setInputFiles() works; Puppeteer uploadFile(path) does not (same as Browserless). |
| downloads | rejected | No download retrieval endpoint; fetch in the page instead. See downloads. |
| built-in residential proxies | rejected | Bring your own proxy. See proxies. |
Migration checklist
- Swap the endpoint and token.
- Remove
profile,stealthand other rejected parameters; move login state to PlaywrightstorageStateor saved cookies. - If you used Browserless’s proxies, pass your own proxy with
proxy=(plusproxyCountryandproxyLocaleMatch=trueif you want the locale to match). - Handle 429 with
Retry-After, or addqueueTimeout. - Run your suite once and check
X-Flatbrowser-Ignored-Params.