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
// 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 hereimport 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);# 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']}"})// 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 },
});
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" },
});
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.
Built by a team that operates browser fleets in production for clients today.