Skip to content
flatbrowser

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

connect.mjs
// 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 (or Authorization: Bearer). Create one in the dashboard.
  • Playwright users: keep chromium.connectOverCDP(). If you used chromium.connect() against /chromium/playwright, switch to connectOverCDP — see connect URLs.
  • Concurrency: Browserless queues extra connections; we answer 429 with Retry-After unless you pass queueTimeout (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.

ignored.sh
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: blockAds

Connect URLs

pathstatusnotes
/ · /chromium · /chromesupportedCreates a session; the browser is Chromium in every case.
/chromium/playwright · /playwright/chromium (and the chrome forms)supportedWith connectOverCDP(). A chromium.connect() client (Playwright wire protocol) gets 400 playwright_protocol_not_supported instead of hanging.
/devtools/browser/<id>supportedAttach to an existing session; same as /session/<id>.
/session/<id>nativeAttach to a session created with POST /v1/sessions or kept with keepalive.
/chromium/stealth · /chrome/stealthrejectedNo stealth mode: standard headless Chromium only.
/firefox · /webkit · /edge and their playwright formsrejectedChromium only.
/devtools/page/<id>rejectedConnect at browser level and pick the page there.

HTTP discovery

endpointstatusnotes
GET /json/version (also under /chromium, /chrome)supportedFor 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/protocolrejected404: 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.

parameterstatusnotes
token · apiKey · api_keysupportedOr Authorization: Bearer.
timeoutsupportedTotal 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>supportedYour upstream proxy (http, https, socks4/4a/5/5h, credentials allowed). See proxies.
proxy=residential · proxy=datacenterrejected400 residential_proxy_not_available: there is no managed proxy pool. Bring your own proxy URL.
proxyCountry · proxyLocaleMatch · proxyTimezonesupportedOnly 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 · proxyPresetignoredWith your own proxy (stickiness is your proxy vendor’s setting); without one: rejected as above.
launchsupportedURL-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>=…supportedForwarded 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-dirrejectedNo persistent profiles: every session starts empty. Keep login state client-side (storageState, cookies).
stealth · humanlike · solveCaptchasrejectedNot offered, and not planned. A false value is ignored.
headless=false · launch.devtoolsrejectedSessions always run headless. headless=true/new is ignored.
record · replay · blockConsentModalsrejectedNot offered.
sessionIdrejectedAttach with /session/<id> instead.
blockAds · trackingId · ttlignoredSafe to drop: they change nothing about egress, identity or state.
keepalive · idleTimeout · maxDuration · queueTimeout · locale · timezone · affinitynativeSee 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).

endpointstatusnotes
POST /contentsupportedReturns text/html, like Browserless.
POST /screenshotsupportedPNG or JPEG (options.type, quality, fullPage, clip, omitBackground) and selector. WebP and encoding: base64 are rejected.
POST /pdfsupportedAll common options: format, landscape, printBackground, scale, width/height, margin, header/footer templates, pageRanges, preferCSSPageSize.
POST /scrapesupportedEach match returns text and html; Browserless’s attributes and box positions are not included.
/chromium/<action> · /chrome/<action>supportedSame as the unprefixed paths.
POST /function · /download · /export · /performance · /unblockrejected404. Use a CDP session for custom page logic.
POST /session (Session API)nativeUse POST /v1/sessions; its connectUrl needs your token appended.
/sessions · /pressure · /activerejected404. Your active sessions: GET /v1/sessions.

Request body keys on the Browserless-style paths

keystatusnotes
url · htmlsupportedExactly one of them. HTML documents up to 4 MB (body limit 5 MB).
gotoOptionssupportedwaitUntil (networkidle0/2 map to networkidle), timeout, referer.
waitForSelector · waitForTimeoutsupportedUp to 30 s.
viewport · userAgent · emulateMediaType · setJavaScriptEnabledsupportedViewport up to 7680 × 4320.
cookies · setExtraHTTPHeaders · authenticatesupportedUp to 50 cookies and 50 headers.
rejectResourceTypes · bestAttemptsupported
waitForFunction · waitForEvent · addScriptTag · addStyleTag · requestInterceptors · rejectRequestPatternrejectedThey would run your code inside our shared gateway. Use a CDP session for page logic.
anything elseignoredReported in X-Flatbrowser-Ignored-Params.

CDP extensions and session behaviour

featurestatusnotes
Browserless.* CDP methods (reconnect, liveURL, saveProfile, …)rejectedChromium answers “method not found”.
reconnecting after a disconnectnativeUse keepalive or a REST-created session and attach to /session/<id>. See sessions.
multiple pages, contexts and clients per sessionsupportedAll count as one session.
uploadssupportedPlaywright setInputFiles() works; Puppeteer uploadFile(path) does not (same as Browserless).
downloadsrejectedNo download retrieval endpoint; fetch in the page instead. See downloads.
built-in residential proxiesrejectedBring your own proxy. See proxies.

Migration checklist

  1. Swap the endpoint and token.
  2. Remove profile, stealth and other rejected parameters; move login state to Playwright storageState or saved cookies.
  3. If you used Browserless’s proxies, pass your own proxy with proxy= (plus proxyCountry and proxyLocaleMatch=true if you want the locale to match).
  4. Handle 429 with Retry-After, or add queueTimeout.
  5. Run your suite once and check X-Flatbrowser-Ignored-Params.