docs · quickstart
Connect in five minutes
Flatbrowser runs Chromium in worker containers dedicated to your account. You drive it over the standard Chrome DevTools Protocol (CDP) on one WebSocket URL, or call a small REST API for one-shot jobs. There is no SDK: the client libraries you already use are the SDK.
1. Your endpoint and key
Create an API key in the dashboard under api keys. It is shown once and stored only as a hash, so copy it then. Every request carries it, in one of three ways:
| form | example | use it for |
|---|---|---|
?token= | wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY | WebSocket URLs — Puppeteer, Playwright, anything CDP |
Authorization: Bearer | Authorization: Bearer flat_live_YOUR_KEY | REST calls, and Puppeteer’s browserURL form |
?apiKey= / ?api_key= | ?apiKey=… | code written for Browserbase or Steel URLs |
The WebSocket endpoint is wss://gw.flatbrowser.com; REST lives on the same host at https://gw.flatbrowser.com. Keep keys out of client-side code and logs: a URL with ?token= is a secret.
2a. Puppeteer
Use puppeteer-core (no bundled browser download) and replace puppeteer.launch() with puppeteer.connect():
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY',
});
const page = await browser.newPage();
await page.goto('https://example.com');If your code discovers the browser over HTTP with browserURL instead, pass the key as a header: Puppeteer builds the discovery URL from the origin and drops any ?token= query string.
const browser = await puppeteer.connect({
browserURL: 'https://gw.flatbrowser.com',
headers: { Authorization: 'Bearer flat_live_YOUR_KEY' },
});2b. Playwright for Node
Use chromium.connectOverCDP(). Playwright’s other method, chromium.connect(), speaks Playwright’s own wire protocol, which the gateway does not; it answers that with a 400 that says so.
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP(
'wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY',
);
const page = await browser.newPage();
await page.goto('https://example.com');2c. Playwright for Python
The same in Python: connect_over_cdp, not connect. The sync API works the same way.
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
"wss://gw.flatbrowser.com?token=flat_live_YOUR_KEY"
)
page = await browser.new_page()
await page.goto("https://example.com")
print(await page.title())
await browser.close()
asyncio.run(main())2d. Or just curl
For a screenshot, a PDF or a page’s HTML you do not need a CDP client at all:
curl -X POST https://gw.flatbrowser.com/v1/screenshot \
-H 'Authorization: Bearer flat_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com", "fullPage": true}' \
-o screenshot.pngEvery endpoint is in the REST reference.
3. What happens when you connect
- The gateway checks the key and your plan, then starts a fresh Chromium in one of your own workers. Nothing is shared with another customer’s browser, and every session starts with an empty profile.
- Your plan caps how many sessions run at once: Solo 2, Team 6, Scale 20. One more gets HTTP 429 with a
Retry-Afterheader — or waits for a free slot if you passqueueTimeout. See errors & limits. - A session you open over the WebSocket ends when that connection closes, unless you ask for
keepalive. Sessions you create through the REST API outlive connections and can be re-attached. See sessions. - Chromium runs headless, on our EU infrastructure, with its own datacenter IP — or through your upstream proxy if you pass one.
- The browser is Chromium (no Firefox or WebKit).
GET /json/versionreturns the build your sessions run.
Changelog
- 2026-10 — public docs. Chromium 153. Browserless-style REST bodies on
/content,/screenshot,/pdfand/scrape;timeoutnow means total session lifetime;keepaliveandqueueTimeout;Retry-Afteron every 429 and 503; unsupported Browserless parameters refused with a 400 that names them instead of being ignored.
Next
- Sessions — Lifecycle, reconnecting, timeouts, keepalive, files and isolation.
- REST API — Sessions, screenshot, PDF, content and scrape endpoints.
- Proxies — Bring your own upstream proxy: sticky sessions, locale matching, WebRTC.
- Errors & limits — The error envelope, every code, Retry-After and the hard limits.
- Migrating from Browserless — What works after an endpoint swap, what is ignored, what is refused.