BAP Quickstart
Install the BAP SDK, connect to a BrowserQL endpoint, and run a session that navigates to a page and reads its title. By the end you'll have a working script and know which connection options control timeouts and transport.
- A Browserless API token from your account dashboard
- npm 11.10 or later for the TypeScript SDK, or Python 3.11 or later for the Python SDK
Get your API token
Copy your token from the account dashboard before continuing. BAP appends it to the endpoint as ?token= when the WebSocket opens, so a missing token fails at connection time rather than on your first method call.
Install the library
- TypeScript
- Python
npm install @browserless.io/bap-ts
python -m pip install bap-py
Connect and run
Swap in your token and this runs as-is:
- TypeScript
- Python
import Browserless from "@browserless.io/bap-ts";
const TOKEN = "YOUR_API_TOKEN_HERE";
// connect() is synchronous and opens no socket. The WebSocket opens on newPage().
const browser = Browserless.connect({
browserWSEndpoint: "wss://production-sfo.browserless.io/chromium/bql",
token: TOKEN,
});
const page = await browser.newPage();
await page.goto("https://example.com");
console.log(await page.title());
// close() closes every page and its socket, freeing your account concurrency.
await browser.close();
import bap.sync_api as bap
TOKEN = "YOUR_API_TOKEN_HERE"
# connect() opens no socket. The WebSocket opens when browser.page() is entered.
with bap.Browserless.connect(
browser_ws_endpoint="wss://production-sfo.browserless.io/chromium/bql",
token=TOKEN,
) as browser:
# The context manager closes the page and the browser when the block exits.
with browser.page() as page:
page.goto("https://example.com")
print(page.title())
Async applications import bap instead of bap.sync_api. The async API uses the same class and method names with async with and await.
Expected output
Example Domain
Pick an endpoint
The quickstart uses Chromium. Swap the path for a different browser:
wss://production-sfo.browserless.io/chromium/bql # Chromium, the default
wss://production-sfo.browserless.io/chrome/bql # Google Chrome
wss://production-sfo.browserless.io/stealth/bql # Managed stealth browser
The endpoint must end in /bql. The same host also serves CDP endpoints such as /chromium, and those don't speak BrowserQL. See Connection URLs and Endpoints for every region and route.
Connection options
- TypeScript
- Python
| Option | Type | Default | Description |
|---|---|---|---|
browserWSEndpoint | string | — | BrowserQL WebSocket endpoint. Optional only when you supply a custom transport that owns its own connection |
token | string | — | Your API token. Appended to the endpoint as ?token= when the connection opens |
timeout | number | 30000 | Default per-operation timeout in milliseconds |
transport | TransportFactory | Built-in WebSocketTransport | Replace the built-in socket with your own transport, Puppeteer-style |
Each call to browser.newPage() opens its own WebSocket connection, so a script that runs three pages holds three sockets until you call close().
| Option | Type | Default | Description |
|---|---|---|---|
browser_ws_endpoint | str | None | None | BrowserQL WebSocket endpoint. Optional only when you supply a custom transport that owns its own connection |
token | str | None | None | Your API token. Appended to the endpoint as ?token= when the connection opens |
timeout | float | 30000 | Default per-operation timeout in milliseconds |
transport | Custom transport | Built-in WebSocket transport | Replace the built-in socket with your own transport |
Each call to browser.page() or browser.new_page() opens its own WebSocket connection, so a script that runs three pages holds three sockets until they're closed.
Runtime notes
Each SDK has one runtime detail worth knowing before you build on it.
- TypeScript
- Python
Node.js and browser builds
The package is isomorphic. Node.js uses the ws package, and bundlers targeting the browser (Vite, webpack, esbuild, Rollup) pick the native WebSocket build through the browser export condition, so no Node built-ins end up in your bundle.
screenshot() and pdf() resolve to a Uint8Array on both platforms. In Node.js the value is a Buffer, which is itself a Uint8Array. The path option writes to disk in Node.js only. In the browser it rejects, so use the returned bytes:
// Browser: turn the bytes into something renderable.
const bytes = await page.screenshot({ type: "png" });
const url = URL.createObjectURL(new Blob([bytes], { type: "image/png" }));
// querySelector returns null when the element is absent, so guard before assigning.
const img = document.querySelector("img");
if (img) {
img.src = url;
}
Node.js sends a User-Agent of @browserless.io/bap-ts/<version> on the WebSocket handshake, so those sessions are identifiable as SDK traffic server-side. Browsers forbid custom handshake headers, so the browser build can't send it. Don't use that header to measure browser-build usage, because it never arrives.
Running BAP in the browser exposes your bundled token to the client and requires the endpoint to accept the origin you connect from. Only do this when your token-delivery policy allows that exposure.
Async and sync surfaces
The package ships two API surfaces with matching class and method names: bap for asyncio applications, and bap.sync_api for synchronous code.
# Synchronous code.
import bap.sync_api as bap
# Asyncio applications: same names, with async with and await.
import bap
The sync surface isn't a separate transport. It runs the same async implementation on a background event-loop thread and blocks the calling thread until each call resolves. Importing bap.sync_api is fine even inside an asyncio application. The restriction is calling its methods from the thread that already owns a running event loop, since blocking that thread would deadlock the loop it's supposed to drive.
FAQ & Troubleshooting
My connection fails immediately
Check that the endpoint ends in /bql. wss://production-sfo.browserless.io/chromium is a CDP endpoint and won't speak BrowserQL. Then confirm your token is set, since it's appended when the socket opens and a missing one fails at connect time.
Why did my operation time out after 30 seconds?
30000 is the default per-operation timeout, in milliseconds. Raise it globally with timeout on Browserless.connect(), or per call with the timeout option that navigation, selector, and CAPTCHA methods accept. Error handling covers the third option, preferences(), and which one wins when they disagree.
Do I need to call browser.close()?
TypeScript: yes, always. close() closes every page and its WebSocket, and leaving sessions open holds a browser against your account concurrency until it times out.
Python: no, if you use the with blocks shown above, since they close the page and browser sockets automatically when the block exits. Call browser.close() explicitly only if you're managing the browser without context managers.
(Python) Why does calling a sync method from my running event loop fail?
Use the async API (import bap) for code that runs on a thread that already owns an event loop, such as inside asyncio or Jupyter. Importing bap.sync_api is fine anywhere. The sync bridge only rejects being called from the thread that owns a running loop, since blocking that thread would deadlock callbacks and network operations.
(TypeScript) Can I run this in the browser instead of Node.js?
Yes. The package is isomorphic and bundlers pick the native WebSocket build automatically. Two things change: path on screenshot() and pdf() rejects, and your token ends up in the client bundle. See the runtime notes above.