Skip to content
flatbrowser

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:

formexampleuse it for
?token=wss://gw.flatbrowser.com?token=flat_live_YOUR_KEYWebSocket URLs — Puppeteer, Playwright, anything CDP
Authorization: BearerAuthorization: Bearer flat_live_YOUR_KEYREST 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():

puppeteer.mjs
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.

browser-url.mjs
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.

playwright.mjs
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.

connect.py
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:

screenshot.sh
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.png

Every 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-After header — or waits for a free slot if you pass queueTimeout. 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/version returns the build your sessions run.

Changelog

  • 2026-10 — public docs. Chromium 153. Browserless-style REST bodies on /content, /screenshot, /pdf and /scrape; timeout now means total session lifetime; keepalive and queueTimeout; Retry-After on 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.