Clients

SDKs

Official clients for TypeScript, Python, and Go — the same small surface in each language. All of them read BROWSERVIEW_API_KEY (and optionally BROWSERVIEW_BASE_URL) from the environment by default, retry 429/503 responses automatically honoring Retry-After, time out after 90s per request, and absolutize the relative URLs the API returns.

TypeScript

Terminal
npm install @browserview/typescript
example.ts
import { BrowserView } from "@browserview/typescript";import { chromium } from "playwright"; const bv = new BrowserView(); // reads BROWSERVIEW_API_KEY; or { apiKey, baseUrl, maxRetries, timeoutMs } const session = await bv.sessions.create({  startUrl: "https://example.com",});console.log("watch it live:", session.viewer_url); 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"); await bv.sessions.destroy(session.id);

For stealth: true sessions swap playwright for patchright (same API, avoids the Runtime.enable tell) and keep using connectOverCDP; pass region if you need to pin placement — see stealth mode. SDK 0.6 adds proxies, solveCaptchas, timeoutSeconds, idleTimeoutSeconds, keepAlive, region, fingerprint on create and release, update, debug, captcha session methods (snake_case in Python, PascalCase in Go).

ESM only, Node 18+, zero dependencies. Everything lives on bv.sessions:

await bv.sessions.create({            // all fields optional  startUrl, width, height, wait, record, maxLifetimeSeconds,  timeoutSeconds, idleTimeoutSeconds, keepAlive, region,  stealth, fingerprint: { screen: { width, height }, hardwareConcurrency, deviceMemory },  proxies: true /* or { country, type, sticky } */, solveCaptchas,  proxy: { server, username, password, bypass },  userAgent, locale, timezone, geolocation: { lat, lon, accuracy },  downloads, contextId, metadata: { key: "value" },});await bv.sessions.release(id);                               // graceful end (202, idempotent)await bv.sessions.update(id, { timeoutSeconds: 7200 });      // extend / shortenawait bv.sessions.debug(id);                                 // { debugger_url, ws_url, pages }await bv.sessions.captcha(id);                               // solver statusawait bv.sessions.list({ metadata: { job: "crawl-42" } });   // Session[]await bv.sessions.get(id);                                   // fresh URLs/tokens + restarts/degradedawait bv.sessions.destroy(id);await bv.sessions.mintToken(id, { scope: "view", ttlSeconds: 3600 });await bv.sessions.screenshot(id, { format: "jpeg", quality: 80 });  // Uint8Arrayawait bv.sessions.listDownloads(id);                         // { name, size_bytes, modified_ms }[]await bv.sessions.downloadFile(id, "report.pdf");            // Uint8Arrayawait bv.sessions.uploadFile(id, bytes, "avatar.png");       // { name, path, size_bytes }await bv.sessions.solveCaptcha(id, { type: "turnstile", sitekey, url });  // token stringawait bv.sessions.replay(id);                                // one-shot manifest fetchawait bv.sessions.waitForReplay(id, { timeoutMs: 120000, intervalMs: 5000 });

Python

Terminal
pip install browserview
example.py
from browserview import BrowserViewfrom playwright.sync_api import sync_playwright bv = BrowserView()  # reads BROWSERVIEW_API_KEY; or BrowserView(api_key=..., base_url=...) session = bv.create_session(start_url="https://example.com")print("watch it live:", session.viewer_url) with sync_playwright() as p:    browser = p.chromium.connect_over_cdp(        session.cdp_url,        headers={"x-session-token": session.cdp_token},    )    page = browser.contexts[0].pages[0]    page.goto("https://news.ycombinator.com") bv.destroy_session(session.id)

Python 3.9+, synchronous, one dependency (httpx), fully typed. The client is also a context manager (with BrowserView() as bv:).

bv.create_session(start_url=None, width=None, height=None, wait=None, record=None,                  max_lifetime_seconds=None, proxy=None, user_agent=None, locale=None,                  timezone=None, geolocation=None, stealth=None, downloads=None,                  context_id=None, metadata=None)            # -> Sessionbv.list_sessions(metadata={"job": "crawl-42"})             # -> list[Session]bv.get_session(id)bv.destroy_session(id)bv.mint_token(id, "view", ttl_seconds=3600)                # -> SessionTokenbv.screenshot(id, format="png", quality=80)                # -> bytesbv.list_downloads(id); bv.download_file(id, "report.pdf")  # -> list[dict], bytesbv.upload_file(id, content, "avatar.png")                  # -> {"name", "path", "size_bytes"}bv.solve_captcha(id, "turnstile", sitekey, url)            # -> token strbv.get_replay(id)                                          # one-shot manifest fetchbv.wait_for_replay(id, timeout=120.0, interval=5.0)

Go

Terminal
go get github.com/browserview/go
main.go
package main import (    "context"    "fmt"     browserview "github.com/browserview/go") func main() {    client, err := browserview.NewFromEnv() // reads BROWSERVIEW_API_KEY    if err != nil {        panic(err)    }     session, err := client.CreateSession(context.Background(),        browserview.CreateSessionOptions{StartURL: "https://example.com"})    if err != nil {        panic(err)    }    fmt.Println("watch it live:", session.ViewerURL)     defer client.DestroySession(context.Background(), session.ID)    // Drive session.CDPURL with chromedp, rod, or any CDP client.}

Go 1.21+, standard library only. Construct with NewFromEnv(), New(key), or NewWithOptions(key, WithBaseURL(…), WithTimeout(…), WithMaxRetries(…), WithHTTPClient(…)).

client.CreateSession(ctx, browserview.CreateSessionOptions{    StartURL: "...", Width: 1280, Height: 800, Wait: browserview.Bool(true), Record: true,    MaxLifetimeSeconds: 1800, Proxy: &browserview.ProxyConfig{Server: "http://proxy:8080"},    UserAgent: "...", Locale: "en-US", Timezone: "America/New_York",    Geolocation: &browserview.Geolocation{Lat: 40.71, Lon: -74.0},    Stealth: true, Downloads: true, ContextID: "acme-crm",    Metadata: map[string]string{"job": "crawl-42"},})client.ListSessions(ctx); client.ListSessionsByMetadata(ctx, map[string]string{"job": "crawl-42"})client.GetSession(ctx, id); client.DestroySession(ctx, id)client.MintToken(ctx, id, browserview.ScopeView, time.Hour)client.Screenshot(ctx, id, browserview.ScreenshotOptions{Format: "jpeg", Quality: 80})client.ListDownloads(ctx, id); client.DownloadFile(ctx, id, "report.pdf")client.UploadFile(ctx, id, "avatar.png", content)client.SolveCaptcha(ctx, id, browserview.SolveCaptchaOptions{Type: browserview.CaptchaTurnstile, Sitekey: ..., URL: ...})client.GetReplay(ctx, id)client.WaitForReplay(ctx, id, 0)   // 0 = 5s poll interval; bound with a context deadline (default 2 min)

Session replay

Create a session with record: true and fetch its replay after it ends — every SDK ships a waitForReplay helper that polls through finalization. See the session replay guide for the manifest schema.

const session = await bv.sessions.create({  startUrl: "https://example.com",  record: true,});// ... drive the session ...await bv.sessions.destroy(session.id); const replay = await bv.sessions.waitForReplay(session.id);console.log(replay.video?.url);   // seekable WebM of the whole sessionconsole.log(replay.events);       // actions / console / network / errors JSONL // Python: bv.wait_for_replay(session.id)// Go:     client.WaitForReplay(ctx, session.ID, 0)

Error handling

All three SDKs raise a typed error carrying the HTTP status and the API's detail message. A 429 or 503 additionally carries the parsed Retry-After value so your queue can back off precisely (the SDKs already retry three times before surfacing it).

LanguageTypeFields
TypeScriptBrowserViewErrorstatus (0 = no HTTP response), retryAfter?
PythonBrowserViewErrorstatus_code (None = no HTTP response), retry_after
Go*browserview.APIErrorStatusCode, Message, RetryAfter (time.Duration) — use errors.As
try {  await bv.sessions.create();} catch (error) {  if (error instanceof BrowserViewError && error.status === 429) {    await sleep((error.retryAfter ?? 30) * 1000);  }}