Skip to content
flatbrowser

docs · sessions

Sessions

A session is one Chromium process in one of your workers, with its own empty profile. It counts against your plan’s concurrency from the moment it starts until it ends.

Two ways to get one

Connect and go (WebSocket-born)

Connecting to wss://gw.flatbrowser.com/?token=… creates a session for that connection. When the connection closes, the session ends and the slot is free again — no cleanup call needed. This is what puppeteer.connect() and connectOverCDP() do with the quickstart URL.

To survive a dropped connection, add keepalive=<ms>: the session then stays up that long with no client attached, and you can re-attach (below). Its ID is in the X-Flatbrowser-Session-Id header of the handshake response, and in GET /v1/sessions.

keepalive.mjs
// survive a dropped connection for up to 2 minutes
const browser = await puppeteer.connect({
  browserWSEndpoint: 'wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY&keepalive=120000',
});

Create, then attach (REST-created)

POST /v1/sessions creates a session that outlives any single connection. Attach to it at /session/<id>?token=… (Browserless’s /devtools/browser/<id> spelling works too), as many times as you like and from more than one client at once. This is the reconnect story: a worker process that restarts simply attaches again.

create-attach.sh
# 1. create a session that outlives connections
curl -X POST https://gw.flatbrowser.com/v1/sessions \
  -H 'Authorization: Bearer flat_live_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"idleTimeoutMs": 600000, "maxDurationMs": 7200000}'
# → 201 {"id":"3f0c…","connectUrl":"wss://gw.flatbrowser.com/session/3f0c…","wsPath":"/session/3f0c…"}

# 2. attach (and re-attach after a disconnect) — append your key
#    wss://gw.flatbrowser.com/session/3f0c…?token=flat_live_YOUR_KEY

# 3. end it explicitly when you are done
curl -X DELETE https://gw.flatbrowser.com/v1/sessions/3f0c… \
  -H 'Authorization: Bearer flat_live_YOUR_KEY'

How a session ends

triggerapplies todefault
the creating connection closesWebSocket-born sessions without keepaliveimmediately
no client attached for the idle timeoutREST-created sessions, and WebSocket-born ones with keepalive5 minutes (REST); the keepalive value (WebSocket)
the maximum duration is reachedevery session12 hours (also the cap)
DELETE /v1/sessions/:idevery session—
browser.close() from your clientevery session—

The idle timer only runs while no client is attached, so a long job that keeps its connection open is never cut off by it — only by the maximum duration.

Timeouts and keepalive parameters

All values are milliseconds. They go on the WebSocket URL or the REST query string; POST /v1/sessions also takes idleTimeoutMs and maxDurationMs in its body. Values above 12 hours are clamped to 12 hours, values below 1000 are refused, and 0 means “use the default”.

parametermeaning
timeoutTotal session lifetime — the Browserless meaning. On a REST action (screenshot, PDF, …) it is the guard for the whole action instead: default 60 s, at most 180 s.
maxDurationTotal session lifetime (native name). Wins over timeout.
idleTimeoutHow long the session may sit with no client attached before it ends.
keepaliveSame as idleTimeout. On a WebSocket URL it also means the session survives its creating connection.
queueTimeoutWait up to this long (max 60 s) for a free slot instead of getting an immediate 429 or 503.

Both directions of every connection are pinged every 30 seconds, so idle-but-open connections survive load balancers and NATs; your client does not need to send anything to stay connected.

Isolation and state

  • Every session starts with a fresh, empty profile in a temporary directory. Cookies, local storage, cache and downloads are deleted when the session ends; nothing carries over to the next one.
  • There are no persistent profiles (Browserless’s profile= and userDataDir are refused). Keep login state on your side — Playwright’s storageState, or cookies you export and set again with page.setCookie() / context.addCookies().
  • Workers belong to one account. Sessions of different customers never share a Chromium process or a profile.
  • Several tabs and browser contexts per session are fine; they all count as the one session.

Downloads and uploads

  • Downloads: fetch the file inside the page and return the bytes, as below; or, in Playwright, read the response body directly (page.waitForResponse(…) then response.body()).
  • Uploads with Playwright work: setInputFiles() sends the file contents to the remote browser (up to 50 MB per call).
  • Uploads with Puppeteer uploadFile(path) do not: it sends your local path, which does not exist on the worker. Use Playwright, or build the file in the page with a DataTransfer.
download-in-page.mjs
// fetch the file inside the page and hand the bytes back over CDP
const base64 = await page.evaluate(async (url) => {
  const res = await fetch(url, { credentials: 'include' });
  const buf = new Uint8Array(await res.arrayBuffer());
  let bin = '';
  for (const b of buf) bin += String.fromCharCode(b);
  return btoa(bin);
}, 'https://example.com/report.csv');
const bytes = Buffer.from(base64, 'base64');

Restarts and maintenance

When we deploy the gateway, open connections are closed after a short drain period; new connections during the drain get HTTP 503 with Retry-After. REST-created sessions, and WebSocket-born sessions with keepalive, keep running: their connection closes with code 1001 and a reason naming the /session/<id> path to re-attach to. Other WebSocket-born sessions end with their connection. Long jobs should reconnect on close — see errors & limits for the close codes.