Browser control
Tier 3 — high-level actions, observations, profiles, CDP passthrough and recordings.
POST /v1/sessions/{id}/browser/act action batch
GET /v1/sessions/{id}/browser/observe observation only
GET /v1/sessions/{id}/browser/pages list open pages
POST /v1/sessions/{id}/browser/pages open a page
DELETE /v1/sessions/{id}/browser/pages/{pid} close a page
WS /v1/sessions/{id}/cdp raw Chrome DevTools Protocol
POST /v1/browser/profiles create a profile
GET /v1/sessions/{id}/browser/recording fetch a recording
Tier 3 and tier 4. Three levels of control: high-level actions, Playwright over CDP, and raw CDP.
Actions
The recommended surface. Model-friendly, retry-safe, and it returns an observation in the same round trip.
curl -sS "$WB_BASE_URL/sessions/$SESSION/browser/act" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{
"page_id": "pg_1",
"actions": [
{ "type": "navigate", "url": "https://example.com/invoices" },
{ "type": "wait_for", "selector": "table.invoices", "timeout_ms": 10000 },
{ "type": "fill", "ref": "a11y:textbox[name=\"Search\"]", "text": "Q3" },
{ "type": "click", "ref": "a11y:button[name=\"Export\"]" },
{ "type": "wait_for_download", "timeout_ms": 20000 }
],
"observe": ["a11y_tree", "screenshot"]
}'
{
"id": "run_5Lm2Kd",
"status": "succeeded",
"results": [
{ "type": "navigate", "status": "ok", "url": "https://example.com/invoices", "http_status": 200 },
{ "type": "wait_for", "status": "ok", "waited_ms": 412 },
{ "type": "fill", "status": "ok" },
{ "type": "click", "status": "ok" },
{ "type": "wait_for_download", "status": "ok", "path": "/downloads/invoices-q3.csv", "size_bytes": 18422 }
],
"observation": {
"a11y_tree": { "role": "document", "name": "Invoices", "children": [ … ] },
"screenshot_b64": "…",
"url": "https://example.com/invoices"
},
"usage": { "browser_page_s": 6, "egress_bytes": 1842304 }
}
Actions in a batch run in order and stop at the first failure. Everything before the failure has already happened — batches are not transactional, and a batch that clicks "Confirm" before failing has confirmed.
Action types
| Action | Fields |
|---|---|
navigate | url, wait_until (load · domcontentloaded · networkidle) |
click | ref or selector, button, click_count |
fill | ref or selector, text, clear |
select | ref or selector, values |
press | key, modifiers |
scroll | ref, delta_y, delta_x, or to (top · bottom) |
hover | ref or selector |
upload | ref, paths (files already in the session) |
wait_for | selector, ref, url_pattern, text, or ms |
wait_for_download | timeout_ms |
evaluate | expression — returns a JSON-serialisable value |
back, forward, reload | — |
set_cookies, clear_cookies | cookies |
Element references
Prefer ref over selector.
a11y:button[name="Export"] by role and accessible name a11y:textbox[name="Search"] a11y:link[name="Invoice 1042"] a11y:#n42 by node ID from the last observation
The accessibility tree is what the model is shown in the observation, so references into it are
things the model actually saw. CSS selectors reference structure the model cannot see and that
breaks on every redesign. selector remains available for code written by a human against a known
page.
Observations
curl -sS "$WB_BASE_URL/sessions/$SESSION/browser/observe?include=a11y_tree,text&max_tokens=3000" \ -H "Authorization: Bearer $WB_API_KEY"
| Observation | Size | Use |
|---|---|---|
a11y_tree | 1–5k tokens, pruned | What to give the model — the default |
text | 0.5–10k tokens | Readability-extracted main content |
screenshot | 50–400 KiB | Visual grounding, debugging |
dom_snapshot | 20–200k tokens | Scraping, debugging. Rarely right for a model |
har | Variable | Network debugging and audit |
console | Small | JavaScript errors |
max_tokens budgets the accessibility tree: deeper and less interactive nodes are pruned first, so
what survives is the interactive surface the agent can act on. A full DOM snapshot of a modern page
often exceeds a hundred thousand tokens; the pruned tree for the same page is usually under three.
Pages
curl -sS "$WB_BASE_URL/sessions/$SESSION/browser/pages" -H "Authorization: Bearer $WB_API_KEY"
{
"object": "list",
"data": [
{ "id": "pg_1", "url": "https://example.com/invoices", "title": "Invoices", "active": true },
{ "id": "pg_2", "url": "https://example.com/help", "title": "Help", "active": false }
]
}
Every open page bills browser_page_s concurrently. An agent that opens tabs and never closes them
multiplies its cost. Close pages when done.
Profiles
A profile is persistent browser identity: cookies, local storage, IndexedDB.
curl -sS "$WB_BASE_URL/browser/profiles" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "acme-portal", "end_user_id": "eu_customer_4821", "region": "bom1" }'
curl -sS "$WB_BASE_URL/sessions" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{ "tier": 3, "browser": { "profile": "bprof_9Lm2Kd" } }'
The profile is loaded at session start and persisted at session end. Only one session may use a profile at a time.
A profile holds live session cookies and is therefore a credential: encrypted at rest with a per-organization key, never shared across projects, and best scoped to one end user. Treat a leaked profile the way you would treat a leaked password.
CDP
Playwright
import { chromium } from 'playwright'
const session = await wb.sessions.create({ tier: 3 })
const browser = await chromium.connectOverCDP(session.browser.cdp_url)
const page = browser.contexts()[0].pages()[0]
await page.goto('https://example.com')
Existing Playwright code works unchanged. Note that Playwright's own retry and wait helpers run client-side, so a chatty script from another region pays a round trip per call — run the client in the session's region.
Raw
wss://connect.workspaces.build/v1/sessions/{id}/cdp?token=wb_st_…
Full protocol. The platform authenticates, proxies and records; it does not restrict CDP domains
beyond what the egress policy already enforces. Network.setRequestInterception works, but note
that the egress proxy has already filtered what the browser can reach.
Recording
curl -sS "$WB_BASE_URL/sessions" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{
"tier": 3,
"browser": {
"recording": { "video": true, "har": true, "trace": true, "retention_days": 7 }
}
}'
curl -sS "$WB_BASE_URL/sessions/$SESSION/browser/recording?kind=video" \ -H "Authorization: Bearer $WB_API_KEY" -o session.webm
Off by default. A recording of a logged-in session contains whatever was on screen, including
credentials typed into a form, so enabling it is a deliberate choice with an explicit retention
period. Billed as recording_gib_month.
Downloads
Downloads land in /downloads, are scanned, and are readable through the filesystem API. They are
never executed automatically, and at tier 3 the CLI allowlist typically has nothing that could
execute them anyway.
curl -sS "$WB_BASE_URL/sessions/$SESSION/fs/read?path=/downloads/invoices-q3.csv" \ -H "Authorization: Bearer $WB_API_KEY" -o invoices.csv
Operational notes
- Bot defences. Sessions egress from platform address space, which some sites block. Attach a customer-supplied upstream proxy on the egress policy when that matters.
networkidleis unreliable on pages with long-polling or analytics beacons. Preferwait_foron a selector that proves the content you need has arrived.evaluateis powerful and unconstrained within the page. It runs in the page's context, so it can read anything the page can, including any logged-in session data.