Docs

Zero to a running
browser in two minutes.

The only BrowserFleet-specific step is creating the session. Everything after is the CDP client you already know. Every sample on this page runs against the live API.

1 · Get a key

Sign up and copy your API key. New accounts get $2 of credit — five hours on Apple Silicon, forty on Standard. Install the SDK if you're in Node; skip it and call the REST API otherwise.

npm i @browserfleet/sdk
Playwright · Node
// npm i @browserfleet/sdk playwright
import { BrowserFleet } from "@browserfleet/sdk";
import { chromium } from "playwright";

const bf = new BrowserFleet({ apiKey: process.env.BROWSERFLEET_API_KEY });

const session = await bf.createSession({
  image: "chrome-stable",
  machineClass: "mac-shared",   // or browser-standard
});

const browser = await chromium.connectOverCDP(BrowserFleet.cdpUrl(session));
const page = await browser.newPage();
await page.goto("https://example.com");

await bf.endSession(session.id); // billing stops here
Puppeteer · Node
import puppeteer from "puppeteer-core";
import { BrowserFleet } from "@browserfleet/sdk";

const bf = new BrowserFleet({ apiKey: process.env.BROWSERFLEET_API_KEY });
const session = await bf.createSession({ image: "chrome-stable", machineClass: "browser-standard" });

const browser = await puppeteer.connect({ browserWSEndpoint: BrowserFleet.cdpUrl(session) });
const page = await browser.newPage();
await page.goto("https://example.com");

await bf.endSession(session.id);
Python · REST + Playwright
# pip install playwright requests
import os, requests
from playwright.sync_api import sync_playwright

r = requests.post("https://api.browserfleet.io/v1/sessions",
    headers={"Authorization": f"Bearer {os.environ['BROWSERFLEET_API_KEY']}"},
    json={"surface": "browser", "image": "chrome-stable", "machineClass": "mac-shared"})
session = r.json()
cdp = next(e["url"] for e in session["endpoints"] if e["transport"] == "cdp")

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(cdp)
    page = browser.new_page()
    page.goto("https://example.com")

requests.delete(f"https://api.browserfleet.io/v1/sessions/{session['id']}",
    headers={"Authorization": f"Bearer {os.environ['BROWSERFLEET_API_KEY']}"})
Raw CDP · any language
// no SDK, no driver — the raw protocol
const res = await fetch("https://api.browserfleet.io/v1/sessions", {
  method: "POST",
  headers: { authorization: `Bearer ${process.env.BROWSERFLEET_API_KEY}`, "content-type": "application/json" },
  body: JSON.stringify({ surface: "browser", image: "chrome-stable", machineClass: "browser-standard" }),
});
const session = await res.json();
const cdpUrl = session.endpoints.find(e => e.transport === "cdp").url;

const ws = new WebSocket(cdpUrl);          // any CDP client from here
ws.onopen = () => ws.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));

A session's endpoints carry short-lived signed URLs. cdp is always present; vnc and control appear when you create with vnc: true.

Choosing a machine

mac-sharedflagship

Real Apple M2 and a Metal GPU. Use it when the fingerprint is the point — the site reads the GPU and the platform. $0.40/hr.

browser-standard

Headful real Chrome on Linux. The default for volume work where a Linux platform is acceptable. $0.05/hr.

browser-perf

3 vCPU and 6 GB for heavy pages — video, WebGL, or JavaScript that fights back. $0.12/hr.

Also browser-nano at $0.03/hr for cooperative sites. Query GET /v1/catalog for the live list with prices.

Persistent profiles

A profile is logged-in state that outlives the session. Create it once, run sessions on it, and the next one starts already inside. State is saved when a session on that profile ends — there is no save call, on purpose.

// 1. create a profile once — it's the customer's logged-in state
const profile = await bf.createProfile("shop-eu");

// 2. run a session on it, log in, do work…
const s1 = await bf.createSession({
  image: "chrome-stable", machineClass: "mac-shared",
  profile: { mode: "managed", id: profile.id },
});
// …end it. The state is saved automatically because the session ran on the profile.
await bf.endSession(s1.id);

// 3. next week: a new session on the same profile is already logged in
const s2 = await bf.createSession({
  image: "chrome-stable", machineClass: "mac-shared",
  profile: { mode: "managed", id: profile.id },
});
A profile is created for a machine class and will only run on it. Its device identity — the GPU, the platform — is frozen at creation, so a login made on a Mac never suddenly reports a Linux box. Two sessions can't open one profile at once: the second gets 409.

Also: listProfiles(), duplicateProfile() to fork a logged-in state, exportProfile() for a signed download of your data, deleteProfile(). All in the dashboard too.

Live view & control

Create with vnc: true and the session also exposes a VNC stream and a control channel. The control channel drives a real cursor with humanised motion at the OS level — not synthetic DOM events — so what you do by hand looks like what a person did.

// vnc: true → the session also exposes a live view and a control channel
const session = await bf.createSession({
  image: "chrome-stable", machineClass: "mac-shared", vnc: true,
});

BrowserFleet.vncUrl(session);        // drop into any noVNC viewer, or open it in the dashboard

const c = await bf.control(session); // real, humanised OS-level input — not synthetic events
await c.move(640, 400);
await c.click("button[type=submit]");
await c.type("hello");

Your own proxy

Point a session at your residential or mobile pool. No markup. Egress is fail-closed: if the proxy can't be reached the browser refuses to launch, so a run can never silently fall back to a datacenter IP.

// bring your own proxy — fail-closed: if it can't be reached, the browser does not launch
const session = await bf.createSession({
  image: "chrome-stable", machineClass: "browser-standard",
  vnc: true,                                // proxies run on the interactive image today
  proxy: { mode: "byo", url: "http://user:pass@gw.yourproxy.com:8080" },
});
Today a proxy requires vnc: true (it runs on the interactive image). http, https and socks5 endpoints are supported; socks5 with credentials isn't yet — use an http(s) endpoint. Managed residential egress is on the roadmap.

Timeouts

Three knobs, all in seconds. Set them so a forgotten session closes itself.

await bf.createSession({
  image: "chrome-stable", machineClass: "browser-standard",
  sessionTtlSec: 3600,   // hard lifetime — plan-capped (Free 15 min · Team 1 h · Enterprise 24 h)
  idleTimeoutSec: 300,   // connected but no CDP traffic this long → closes; 0 disables
  keepAliveSec: 120,     // after you disconnect, stay up this long for a reconnect
});

Pass an Idempotency-Key header (or idempotencyKey in the SDK) when you retry a create — the same key returns the same session instead of a second one.

Everything above runs on the free credit.

Two minutes from key to a real browser. If something in these docs doesn't work exactly as written, that's a bug — tell us.

Get an API key Talk to us

Built by a team that operates browser fleets in production for clients today.