A session is one whole browser process, yours alone, claimed from a warm pool and ready to drive. Use a session when your agent needs to click through an app, fill forms, or keep state across many steps. For a single page, a scrape with actions is cheaper.

Create a session

bash
curl -s https://beta-api.b3os.org/v1/vision/sessions \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"viewport": {"width": 1440, "height": 900}, "timeout_s": 600}'

The session is active until you close it or timeout_s passes (300 seconds by default, at most 1,800). It bills vision.browser_ms from claim to close. Other fields: profile to start signed in, persist_profile to save cookies back when it closes, locale, timezone, user_agent, block_ads and block_resources.

Drive it with batched actions

POST /v1/vision/sessions/{session_id}/act runs a list of browser actions in order on one page and returns every result in one round trip, so an agent pays one call for a whole sequence:

bash
curl -s "https://beta-api.b3os.org/v1/vision/sessions/$SESSION_ID/act" \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"actions": [ {"kind": "navigate", "url": "https://app.example.com/login"}, {"kind": "type", "target": {"selector": "#email"}, "text": "agent@example.com"}, {"kind": "click", "target": {"role": "button", "name": "Continue"}}, {"kind": "wait", "for": {"selector": "[data-test=\"dashboard\"]"}}, {"kind": "markdown"} ]}'

Each result has its kind, status (ok, failed or skipped) and duration_ms, plus what the action produced: the URL and status code for navigate, the value for evaluate, the image for screenshot, the page for markdown. Execution stops at the first failed action unless continue_on_error is set; later actions are reported as skipped, and the failed one carries its error, such as target_not_found. A batch holds up to 100 actions and must finish within timeout_ms (30 seconds by default, 60 at most). Set target_id to act on a page other than the most recently active one; GET on the session lists its pages.

Browser actions

KindFieldsDoes
navigateurl, wait_until (load, domcontentloaded, networkidle)Load a URL
clicktarget, button, count, modifiersClick an element
hovertargetMove the pointer over an element
typetext, target, clear, interval_msType into the focused or targeted field
keykey (such as Enter), modifiersPress a key
scrollto, by or targetScroll to a point, by an amount, or until the target is in view
selecttarget, valueChoose an option in a select
waitms, or for one of selector, text, url, network_idleWait a fixed time or for a condition
evaluateexpressionRun JavaScript in the page and return its JSON result
screenshottarget, full_page, format, qualityCapture the page or an element
markdownonly_main_contentReturn the page as Markdown

A target matches by CSS selector, visible text (exact for a whole match) or ARIA role plus accessible name, optionally within a scope; nth picks one of several matches. Actions that wait for a target take timeout_ms (10 seconds by default). Render scripts use the same kinds and fields, except evaluate, screenshot and markdown.

Connect over CDP

Every session has a cdp_url, a WebSocket that speaks the Chrome DevTools Protocol for that session only. Point Playwright or Puppeteer at it with your key in the Authorization header:

ts
import { chromium } from "playwright";const browser = await chromium.connectOverCDP(session.cdp_url, { headers: { Authorization: `Bearer ${process.env.B3OS_API_KEY}` },});const page = browser.contexts()[0].pages()[0] ?? (await browser.contexts()[0].newPage());await page.goto("https://example.com");console.log(await page.title());

The CDP proxy exposes only the session's own pages. It refuses SystemInfo.*, Tethering.*, Extensions.* and Cast.*, and every Browser.* method except the ones Playwright and Puppeteer need to drive a browser (Browser.getVersion, the permission methods, window bounds, Browser.setDownloadBehavior and Browser.cancelDownload). It also refuses other sessions' targets, host file access (DOM.setFileInputFiles and file drags), downloads to host paths (downloads land in the session's own directory, wiped with the browser), and navigation to anything but http:, https: and about:blank. A refused call gets a CDP error with code cdp_method_blocked. Creating a browser context with its own proxy (proxyServer, proxyBypassList or originsWithUniversalNetworkAccess, which Playwright's newContext({proxy}) sends) is refused with forbidden: all of the session's traffic goes through its own egress route. A client that falls more than a few megabytes behind the stream is disconnected.

From a browser page, where you cannot set headers, get a single-use stream ticket with POST /v1/vision/sessions/{session_id}/ticket and open the socket within 30 seconds with ?ticket=tkt_....

Close the session

bash
curl -s -X DELETE "https://beta-api.b3os.org/v1/vision/sessions/$SESSION_ID" \ -H "Authorization: Bearer $B3OS_API_KEY"

Closing destroys the browser and returns the session with its final usage.browser_ms. With persist_profile, cookies and storage are saved to the profile first. Closing a closed session returns it unchanged. Sessions that are closed or expired stay listed for 7 days; calling act on one fails with session_closed.

Ask a question... ⌘I