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
npm install @browserview/typescriptimport { 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
pip install browserviewfrom 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
go get github.com/browserview/gopackage 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).
| Language | Type | Fields |
|---|---|---|
| TypeScript | BrowserViewError | status (0 = no HTTP response), retryAfter? |
| Python | BrowserViewError | status_code (None = no HTTP response), retry_after |
| Go | *browserview.APIError | StatusCode, 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); }}