TypeScript client for the browserview.io API. browserview.io runs disposable cloud Chromium sessions that humans can watch and control in a live viewer while agents drive the same browser over the Chrome DevTools Protocol.
Zero runtime dependencies. Requires Node 18+ (any runtime with fetch works).
npm install @browserview/typescript| Setting | Option | Env var | Default |
|---|---|---|---|
| API key | apiKey |
BROWSERVIEW_API_KEY |
— (required) |
| Base URL | baseUrl |
BROWSERVIEW_BASE_URL |
https://sessions.browserview.io |
| Retries | maxRetries |
— | 3 (0 disables) |
| Timeout | timeoutMs |
— | 60000 |
Explicit constructor options take precedence over environment variables.
Two kinds of API keys work:
- Tenant keys minted by the browserview.io console, format
bv_live_+ 40 hex chars. - Shard admin keys, which are arbitrary strings (often bare hex). Admin keys unlock
capacity(), theowneroption oncreate()(create a session on behalf of a tenant), and cross-tenant access.
The SDK sends the key as Authorization: Bearer <key>; the API equally accepts x-api-key: <key>.
import { BrowserView } from "@browserview/typescript";
const bv = new BrowserView(); // reads BROWSERVIEW_API_KEY
// or: new BrowserView({ apiKey: "...", baseUrl: "https://sessions.browserview.io" })
const session = await bv.sessions.create({
startUrl: "https://example.com", // server default: "about:blank"
width: 1280, // server default (320–3840)
height: 800, // server default (240–2160)
});
console.log(session.viewer_url); // live viewer, control access
console.log(session.watch_url); // live viewer, view-only
// ...when you're done:
await bv.sessions.destroy(session.id);create() blocks until the browser is ready by default (server-side wait: true, typically ~5s). Pass wait: false to return immediately; the SDK only sends fields you set. With an admin key, owner: "<user id>" creates the session on behalf of a tenant (the server ignores owner for tenant keys).
Other operations:
const sessions = await bv.sessions.list(); // no URLs/tokens in list responses
const fresh = await bv.sessions.get(session.id); // fresh URLs/tokens + restarts/degraded
const token = await bv.sessions.mintToken(session.id, {
scope: "view", // "view" | "control" | "cdp"
ttlSeconds: 3600, // 1..604800 (7 days); default 3600
});get() additionally reports session health: restarts (number of browser restarts, or null when the in-container status server is unreachable) and degraded (true once the browser has restarted). All session objects include mem_limit_bytes, the container memory limit.
The server returns viewer_url, watch_url, and cdp_url as relative paths; the SDK absolutizes them against your base URL before you see them.
Connect an agent to the same browser a human is watching in the viewer:
import { chromium } from "playwright";
const session = await bv.sessions.create({ startUrl: "https://example.com" });
const browser = await chromium.connectOverCDP(session.cdp_url, {
headers: { "x-session-token": session.cdp_token },
});
const page = browser.contexts()[0].pages()[0];
await page.goto("https://news.ycombinator.com");The token can also be passed as ?token= on the CDP URL. Puppeteer's puppeteer.connect({ browserURL }) works the same way.
Create the session with record: true and BrowserView captures everything server-side — a video of the display plus structured streams of actions, console output, network requests, and errors:
const session = await bv.sessions.create({
startUrl: "https://example.com",
record: true,
});
// ... drive the session ...
await bv.sessions.destroy(session.id);
// The replay is ready seconds after the session ends.
const replay = await bv.sessions.waitForReplay(session.id); // polls up to 2 min
console.log(replay.video?.url); // seekable WebM
console.log(replay.pages); // main-frame navigation timeline
console.log(replay.events?.console?.url); // JSONL: {"ts": ..., "level": ..., ...}sessions.replay(id) fetches the manifest without polling: it returns { status: "recording" } while the session is alive and throws a 404 BrowserViewError while the recording finalizes. Every event line carries an absolute epoch-ms ts; align it with the video via (ts - replay.video.start_time_ms) / 1000 seconds. Artifact URLs expire at urls_expire_at_ms — call replay() again for fresh ones.
Failed calls throw BrowserViewError with status, message, and retryAfter (seconds, parsed from the Retry-After header on any status). status is 0 when no HTTP response was received (e.g. timeout).
The client retries automatically before throwing:
- 429 (rate/capacity limits — creates send
Retry-After: 30) and 503 (auth backend temporarily down —Retry-After: 10) are retried for every method; the server does not commit a session create before returning these. - Network errors and timeouts are retried for idempotent
GET/DELETEonly. - Waits honor
Retry-After(a single wait is capped at 30s), otherwise back off 1s, 2s, 4s. Default 3 retries; configure withmaxRetries(0 disables). - Each attempt is aborted after
timeoutMs(default 60s, sized for create-with-wait).
Other statuses you may see: 401 invalid/missing key (repeated failures escalate to 429 per IP), 403 admin-only endpoint with a tenant key, 404 unknown or un-owned session (tenants never get 403 for foreign sessions), 502 session create/backend failure.
import { BrowserViewError } from "@browserview/typescript";
try {
await bv.sessions.get("nope");
} catch (err) {
if (err instanceof BrowserViewError && err.status === 404) {
// session gone or not yours
}
}const cap = await bv.capacity();
// { max_sessions, active_sessions, mem_total_bytes, mem_reserve_bytes,
// mem_committed_bytes, session_mem_limit_bytes, webrtc_ports_free,
// subnets_free, admittable }Tenant keys receive a 403 ("admin API key required").
GET /healthz and GET /readyz are unauthenticated and return {"status":"ok"} — useful for probes; the SDK does not wrap them.