API Reference
Give LLMs and agents a real browser they can see and control.
https://api.browserbeam.com
Authorization: Bearer YOUR_API_KEY
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /v1/sessions | Create a browser session |
| GET | /v1/sessions/:id | Get session info |
| POST | /v1/sessions/:id/act | Execute steps on a session |
| DELETE | /v1/sessions/:id | Destroy a session |
| GET | /v1/sessions | List active sessions |
Key concepts
Session — an isolated browser tab with its own cookies, storage, and viewport. Usage is billed in credits. Runtime, proxy bandwidth, AI tokens, and CAPTCHA solves all draw from a single credit pool.
Steps — sequential browser actions (click, fill, extract, etc.) sent as a JSON array. On failure, execution stops and the error is returned alongside current page state.
Refs — stable element identifiers (e1, e2, …) assigned during observation. Use refs to target elements in follow-up steps. Refs are reassigned on every observation — always use the refs from the most recent response.
Auto-observe — every successful call returns fresh page state automatically: content, interactive elements with refs, scroll position, and a diff of what changed.
Session lifecycle
Getting Started
Go from zero to your first browser session in under a minute. All you need is an API key.
1. Get your API key
Sign up at browserbeam.com and create an API key from the dashboard.
2. Create a session and navigate to a page
Open your terminal and run:
curl -X POST https://api.browserbeam.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
from browserbeam import Browserbeam
client = Browserbeam(api_key="YOUR_API_KEY")
session = client.sessions.create(url="https://example.com")
print(session.session_id) # ses_abc123
print(session.page.title) # Example Domain
import Browserbeam from "@browserbeam/sdk";
const client = new Browserbeam({ apiKey: "YOUR_API_KEY" });
const session = await client.sessions.create({ url: "https://example.com" });
console.log(session.sessionId); // ses_abc123
console.log(session.page.title); // Example Domain
require "browserbeam"
client = Browserbeam::Client.new(api_key: "YOUR_API_KEY")
session = client.sessions.create(url: "https://example.com")
puts session.session_id # ses_abc123
puts session.page.title # Example Domain
3. Read the response
The response includes the page content as markdown, interactive elements with refs, and scroll position:
{
"session_id": "ses_abc123",
"expires_at": "2026-03-18T14:05:00Z",
"request_id": "req_1a2b3c4d5e6f",
"completed": 1,
"page": {
"url": "https://example.com",
"title": "Example Domain",
"stable": true,
"markdown": {
"content": "# Example Domain\n\nThis domain is for use in illustrative examples..."
},
"interactive_elements": [
{"ref": "e1", "tag": "a", "role": "link", "label": "More information...", "in": "main"}
],
"forms": [],
"map": [
{"section": "header", "selector": "header", "hint": "Example Domain (1 link)"},
{"section": "main", "selector": "main", "hint": "Example Domain (1 link)"},
{"section": "footer", "selector": "footer", "hint": "More information... (1 link)"}
],
"changes": null,
"scroll": {"y": 0, "height": 600, "viewport": 720, "percent": 100}
},
"media": [],
"extraction": null,
"blockers_dismissed": ["cookie_consent"],
"error": null
}
4. Interact with the page
Use the element refs from the response to click, fill, or otherwise interact:
curl -X POST https://api.browserbeam.com/v1/sessions/ses_abc123/act \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"steps": [{"click": {"ref": "e1"}}]}'
session.click(ref="e1")
print(session.page.url) # navigated to new page
await session.click({ ref: "e1" });
console.log(session.page.url); // navigated to new page
session.click(ref: "e1")
puts session.page.url # navigated to new page
5. Destroy the session
Always clean up when done. This frees resources and stops credit consumption:
curl -X DELETE https://api.browserbeam.com/v1/sessions/ses_abc123 \
-H "Authorization: Bearer YOUR_API_KEY"
client.sessions.destroy(session.session_id) # 204 No Content
await client.sessions.destroy(session.sessionId); // 204 No Content
client.sessions.destroy(session.session_id) # 204 No Content
6. Next steps
→ Browse Step Types to see everything you can do (click, fill, extract, screenshot, etc.)
→ See Workflows for common patterns like login flows and data extraction
→ Read Element Targeting to understand refs, text, and label selectors
Authentication
Every request requires an API key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
Get your API key from the dashboard.
Missing or invalid keys return 401:
{
"error": {
"code": "unauthorized",
"message": "Invalid API key"
}
}
POST /v1/sessions
Create a browser session. Optionally navigate to a URL and execute steps in the same call.
Parameters (all optional)
| Param | Type | Default | Description |
|---|---|---|---|
| url | string | — | Navigate to this URL after creation |
| steps | array | — | Steps to execute after navigation |
| timeout | integer | 300 | Session lifetime in seconds (10–1800) |
| viewport | object | {1280, 720} | {width, height} |
| user_agent | string | auto | Custom User-Agent string. When omitted, a realistic user agent is assigned automatically from a rotating pool. |
| locale | string | — | Browser locale (e.g. en-US) |
| timezone | string | — | Timezone (e.g. America/New_York) |
| proxy | object | string | datacenter, auto | All sessions are routed through a proxy. Datacenter is the default; use residential for sites that require it. Pass an object {"kind": "datacenter"|"residential", "country": "us"|"auto"} to customize, or a string URL to bring your own proxy (e.g. http://user:pass@proxy:8080). |
Datacenter countries: br, cn, de, fr, gb, kr, nl, ro, sg, us, euResidential countries (151)ad, ae, af, al, am, ao, ar, at, au, aw, az, ba, bd, be, bg, bh, bj, bn, bo, br, bs, bt, by, bz, ca, cf, ch, ci, cl, cm, cn, co, cr, cu, cy, cz, de, dj, dk, dm, do, dz, ec, ee, eg, es, et, eu, fi, fj, fr, gb, ge, gh, gm, gr, gt, hk, hn, hr, ht, hu, id, ie, il, in, iq, ir, is, it, jm, jo, jp, ke, kh, kr, kw, kz, la, lb, li, lk, lr, lt, lu, lv, ma, mc, md, me, mg, mk, ml, mm, mn, mr, mt, mu, mv, mx, my, mz, na, ng, nl, no, np, nz, om, pa, pe, ph, pk, pl, pr, pt, py, qa, ro, rs, ru, sa, sc, sd, se, sg, si, sk, sn, ss, td, tg, th, tm, tn, tr, tt, tw, tz, ua, ug, us, uy, uz, ve, vg, vn, ye, za, zm, zw | |||
| block_resources | array | ["image", "font"] | Resource types to block. Allowed values: image, font, stylesheet, script. Media (video/audio) is always blocked. Pass [] to unblock images and fonts. |
| auto_dismiss_blockers | boolean | true | Auto-dismiss cookie banners and popups |
| cookies | array | — | Array of cookie objects to inject before navigation. Each requires name, value, and either domain or url. Optional: path, expires, httpOnly, secure, sameSite |
Behavior
| url | steps | Result |
|---|---|---|
| — | — | Bare session, no page loaded |
| set | — | Navigate → auto-observe → page state returned |
| set | set | Navigate → run steps → auto-observe |
| — | set | Run steps (first should be goto) |
Example — navigate to a page
curl -X POST https://api.browserbeam.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
from browserbeam import Browserbeam
client = Browserbeam(api_key="YOUR_API_KEY")
session = client.sessions.create(url="https://example.com")
import Browserbeam from "@browserbeam/sdk";
const client = new Browserbeam({ apiKey: "YOUR_API_KEY" });
const session = await client.sessions.create({ url: "https://example.com" });
require "browserbeam"
client = Browserbeam::Client.new(api_key: "YOUR_API_KEY")
session = client.sessions.create(url: "https://example.com")
{
"url": "https://example.com"
}
{
"session_id": "ses_abc123",
"expires_at": "2026-03-18T14:05:00Z",
"request_id": "req_1a2b3c4d5e6f",
"completed": 1,
"page": {
"url": "https://example.com",
"title": "Example Domain",
"stable": true,
"markdown": {
"content": "# Example Domain\n\nThis domain is for use in illustrative examples..."
},
"interactive_elements": [
{"ref": "e1", "tag": "a", "role": "link", "label": "More information...", "in": "main"}
],
"forms": [],
"map": [
{"section": "header", "selector": "header", "hint": "Example Domain (1 link)"},
{"section": "main", "selector": "main", "hint": "Example Domain (1 link)"},
{"section": "footer", "selector": "footer", "hint": "More information... (1 link)"}
],
"changes": null,
"scroll": {"y": 0, "height": 600, "viewport": 720, "percent": 100}
},
"media": [],
"extraction": null,
"blockers_dismissed": ["cookie_consent"],
"error": null
}
Example — navigate + steps
{
"url": "https://example.com/login",
"steps": [
{
"fill_form": {
"fields": {"Email": "user@test.com", "Password": "secret"},
"submit": true
}
},
{"screenshot": {}},
{"close": {}}
]
}
Example — custom options
{
"url": "https://example.com",
"timeout": 600,
"viewport": {"width": 1920, "height": 1080},
"locale": "en-US",
"timezone": "America/New_York",
"block_resources": [],
"auto_dismiss_blockers": true
}
GET /v1/sessions/:id
Get session status and metadata.
Response fields
| Field | Type | Description |
|---|---|---|
| session_id | string | Session identifier |
| status | string | active, closed, or failed (fatal error — session cannot continue) |
| started_at | string | ISO 8601 creation timestamp |
| ended_at | string|null | ISO 8601 end timestamp, or null if active |
| duration_seconds | integer|null | Total runtime in seconds, or null if active |
| expires_at | string | ISO 8601 expiry timestamp |
| error_code | string|null | When status is failed, engine error code (e.g. access_denied) |
| error_message | string|null | When status is failed, human-readable details |
curl https://api.browserbeam.com/v1/sessions/ses_abc123 \
-H "Authorization: Bearer YOUR_API_KEY"
info = client.sessions.get(session.session_id)
const info = await client.sessions.get(session.sessionId);
info = client.sessions.get(session.session_id)
{
"session_id": "ses_abc123",
"status": "active",
"started_at": "2026-03-18T13:55:00Z",
"ended_at": null,
"duration_seconds": null,
"expires_at": "2026-03-18T14:00:00Z"
}
When the session ended with a fatal error (unsolvable block, 403, etc.):
{
"session_id": "ses_abc123",
"status": "failed",
"started_at": "2026-03-18T13:55:00Z",
"ended_at": "2026-03-18T13:56:20Z",
"duration_seconds": 80,
"expires_at": "2026-03-18T14:00:00Z",
"error_code": "access_denied",
"error_message": "Page returned HTTP 403 — access denied or geo-restricted"
}
POST /v1/sessions/:id/act
Execute steps on an existing session. Steps run sequentially. On failure, execution stops and the error is returned alongside current page state. Auto-observe runs at the end.
Request body
| Param | Type | Required | Description |
|---|---|---|---|
| steps | array | yes | Array of step objects to execute |
Each step is a JSON object with one key (the step type) and its params as the value. See Step Types for all available actions.
curl -X POST https://api.browserbeam.com/v1/sessions/ses_abc123/act \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": [
{"fill": {"ref": "e1", "value": "search query"}},
{"click": {"ref": "e3"}},
{"screenshot": {}}
]
}'
session.fill("search query", ref="e1")
session.click(ref="e3")
session.screenshot()
await session.fill({ ref: "e1", value: "search query" });
await session.click({ ref: "e3" });
await session.screenshot();
session.fill("search query", ref: "e1")
session.click(ref: "e3")
session.screenshot
{
"steps": [
{"fill": {"ref": "e1", "value": "search query"}},
{"click": {"ref": "e3"}},
{"screenshot": {}}
]
}
{
"session_id": "ses_abc123",
"expires_at": "2026-03-18T14:05:00Z",
"request_id": "req_9c4b3dc2e5f6",
"completed": 3,
"page": {
"url": "https://example.com/results?q=search+query",
"title": "Search Results",
"stable": true,
"markdown": {"content": "# Search Results\n\n## 1. First result..."},
"interactive_elements": [
{"ref": "e1", "tag": "input", "role": "search", "label": "Search", "value": "search query", "in": "form", "form": "f1"},
{"ref": "e2", "tag": "a", "role": "link", "label": "First result", "in": "main", "near": "Search Results"},
{"ref": "e3", "tag": "a", "role": "link", "label": "Second result", "in": "main", "near": "Search Results"}
],
"forms": [
{"ref": "f1", "id": "search", "action": "/search", "method": "GET", "fields": ["e1"]}
],
"changes": {
"content_changed": true,
"elements_added": [{"ref": "e2"}, {"ref": "e3"}],
"elements_removed": []
},
"scroll": {"y": 0, "height": 3000, "viewport": 720, "percent": 24}
},
"media": [{"type": "screenshot", "format": "png", "data": "<base64>"}],
"extraction": null,
"blockers_dismissed": [],
"error": null
}
DELETE /v1/sessions/:id
Destroy a session and release browser resources. Runtime is recorded for billing.
Always destroy sessions when done. This frees resources and stops credit consumption. Sessions also auto-expire based on their timeout.
Returns 204 No Content on success. Destroying an already-destroyed session returns 404.
curl -X DELETE https://api.browserbeam.com/v1/sessions/ses_abc123 \
-H "Authorization: Bearer YOUR_API_KEY"
client.sessions.destroy(session.session_id) # 204 No Content
await client.sessions.destroy(session.sessionId); // 204 No Content
client.sessions.destroy(session.session_id) # 204 No Content
# Empty response — session destroyed
GET /v1/sessions
List sessions for the authenticated API key. Supports cursor-based pagination and status filtering.
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
| status | string | all | active, closed, or failed |
| limit | integer | 25 | Results per page (1–100) |
| after | string | — | Cursor from next_cursor in previous response |
curl "https://api.browserbeam.com/v1/sessions?status=active&limit=25" \
-H "Authorization: Bearer YOUR_API_KEY"
result = client.sessions.list(status="active", limit=25)
const result = await client.sessions.list({ status: "active", limit: 25 });
result = client.sessions.list(status: "active", limit: 25)
{
"sessions": [
{
"session_id": "ses_abc123",
"status": "active",
"started_at": "2026-03-18T13:55:00Z"
}
],
"has_more": false,
"next_cursor": null
}
Step Types
Each step is a JSON object with one key (the step type) and its params as the value. Send steps in the steps array of a create or act call.
Steps execute sequentially. On failure, execution stops and the error is returned. An auto-observe runs at the end of every call.
goto
Navigate to a URL. Waits for page load and stability checks.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| url | string | yes | — | URL to navigate to |
| wait_for | string | no | — | CSS selector to wait for after navigation |
| wait_until | string | no | — | JavaScript expression to wait for (must become truthy) |
| wait_timeout | integer | no | 10000 | Max ms to wait for wait_for / wait_until |
{"goto": {"url": "https://example.com"}}
{"goto": {"url": "https://example.com", "wait_for": "#main-content", "wait_timeout": 15000}}
{"goto": {"url": "https://example.com", "wait_until": "window.__APP_READY === true", "wait_timeout": 10000}}
On failure (DNS error, timeout, SSL), returns error code navigation_failed.
observe
Force a page observation. Auto-observe runs at the end of every call, so explicit observe is only needed to change format or scope mid-pipeline.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| format | string | no | markdown | markdown or html |
| mode | string | no | main | main returns main content area. full returns all page sections (nav, aside, footer, etc.) organized by region headers. |
| include_page_map | boolean | no | false | Include a lightweight section map (page.map) showing available page regions with hints. Auto-included on the first observe in a session. |
| scope | string | no | — | CSS selector to limit observation |
| include_links | boolean | no | false | Include links as interactive elements |
| max_text_length | integer | no | 12000 | Content length limit (default 20000 for mode: "full") |
{"observe": {}}
{"observe": {"format": "html"}}
{"observe": {"scope": "#content", "include_links": true}}
{"observe": {"mode": "full", "max_text_length": 20000}}
{"observe": {"include_page_map": true}}
click
Click an element. Exactly one targeting param required (see Element Targeting).
| Param | Type | Required | Description |
|---|---|---|---|
| ref / text / label | string | one required | Element targeting (see below) |
{"click": {"ref": "e3"}}
{"click": {"text": "Submit"}}
{"click": {"label": "Sign in"}}
fill
Clear and replace the value of an input. For character-by-character typing, use type instead.
| Param | Type | Required | Description |
|---|---|---|---|
| ref / text / label | string | one required | Element targeting |
| value | string | yes | Value to fill |
{"fill": {"ref": "e1", "value": "hello world"}}
{"fill": {"label": "Email", "value": "user@example.com"}}
type
Type character-by-character with a delay. Use for autocomplete and search-as-you-type inputs.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| ref / text / label | string | one required | — | Element targeting |
| value | string | yes | — | Text to type |
| delay | integer | no | 50 | Milliseconds between keystrokes |
{"type": {"label": "Search", "value": "browserbeam api", "delay": 100}}
select
Select an option from a <select> dropdown.
| Param | Type | Required | Description |
|---|---|---|---|
| ref / text / label | string | one required | Element targeting |
| value | string | yes | Option value to select |
{"select": {"label": "Country", "value": "US"}}
{"select": {"ref": "e4", "value": "option_value"}}
check
Check or uncheck a checkbox/radio button.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| ref / text / label | string | one required | — | Element targeting |
| checked | boolean | no | true | false to uncheck |
{"check": {"label": "Remember me"}}
{"check": {"label": "Terms", "checked": false}}
scroll
Scroll the page or scroll an element into view.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| to | string | one of to/direction/ref | — | bottom or top |
| direction | string | — | — | up or down |
| amount | integer | no | 500 | Pixels per scroll step |
| times | integer | no | 1 | Repeat scroll N times |
{"scroll": {"to": "bottom"}}
{"scroll": {"direction": "down", "amount": 500, "times": 3}}
{"scroll": {"ref": "e6"}}
scroll_collect
Scroll through the entire page, wait for lazy-loaded content at each position, and return a unified observation. Ideal for infinite scroll and long pages.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| max_scrolls | integer | no | 50 | Safety limit on scroll iterations |
| wait_ms | integer | no | 500 | Pause between scrolls (ms) |
| timeout_ms | integer | no | 60000 | Total time budget (ms) |
| max_text_length | integer | no | 100000 | Content length limit |
{"scroll_collect": {}}
{"scroll_collect": {"max_scrolls": 30, "max_text_length": 50000}}
screenshot
Capture a screenshot. Base64 data appears in the media array with type: "screenshot".
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| full_page | boolean | no | false | Full scrollable page |
| selector | string | no | — | CSS selector to screenshot a specific element |
| format | string | no | png | png or jpeg |
| quality | integer | no | 80 | JPEG quality (1–100) |
{"screenshot": {}}
{"screenshot": {"full_page": true, "format": "jpeg", "quality": 80}}
{"screenshot": {"selector": "#chart"}}
wait
Wait for a condition. Exactly one of ms, selector, text, or until is required.
{"wait": {"ms": 2000}}
{"wait": {"selector": "#results", "timeout": 10000}}
{"wait": {"text": "Loading complete", "timeout": 5000}}
// Wait for a JS expression to become truthy
{"wait": {"until": "document.querySelectorAll('.item').length >= 10", "timeout": 15000}}
extract
Structured data extraction. Keys are your field names, values are CSS/XPath selectors. Results appear in the extraction object of the response.
Selector syntax
// Pattern: "selector >> function"
// Functions: text, href (any attr), json, or omit for raw HTML
"title": "h1 >> text"
"link": "a.main >> href"
"image": "img.hero >> src"
"meta": "meta[name=description] >> content"
"html_block": "div.content"
Arrays
// Wrap in [ ] to collect all matches
"all_links": ["a >> href"]
"headings": ["h2 >> text"]
Repeating structures
// Use [{_parent}] for repeating items like product cards
"products": [
{
"_parent": ".product-card",
"name": "h2 >> text",
"price": ".price >> text",
"url": "a >> href"
}
]
Tables
// Tables are auto-parsed into arrays using headers as keys
"people": "table.contacts"
// → [{"Name": "Alice", "Email": "alice@example.com"}, ...]
JavaScript expressions
// Use js >> prefix to run JavaScript in the browser
"count": "js >> document.querySelectorAll('.item').length"
"title": "js >> document.querySelector('h1').textContent.trim()"
AI-powered selectors
Use the ai >> prefix to describe an element in plain English. The engine resolves it to a real CSS selector via AI, caches it per user and domain, and reuses it on subsequent calls at zero additional cost.
// Mix AI selectors with regular CSS selectors in the same schema
"products": [
{
"_parent": "article.product_pod",
"name": "ai >> the product title",
"price": "ai >> the price including currency symbol",
"stock": ".instock >> text"
}
]
AI selectors consume AI tokens (billed as credits). Once cached, subsequent calls reuse the resolved CSS selector for free. Use refresh_selectors: true inside the extract step to force re-resolution if a site's structure changes.
Full example
{
"url": "https://example.com/products",
"steps": [
{
"extract": {
"page_title": "h1 >> text",
"description": "meta[name=description] >> content",
"nav_links": ["nav a >> text"],
"articles": [
{
"_parent": ".article-card",
"title": "h2 >> text",
"url": "a >> href",
"author": ".byline >> text"
}
],
"stats_table": "table.statistics"
}
},
{"close": {}}
]
}
{
"session_id": "ses_xyz789",
"completed": 2,
"page": { "..." : "..." },
"extraction": {
"page_title": "Our Products",
"description": "Browse our full catalog",
"nav_links": ["Home", "Products", "About", "Contact"],
"articles": [
{"title": "Widget Pro", "url": "https://example.com/widget-pro", "author": "Jane"},
{"title": "Gadget X", "url": "https://example.com/gadget-x", "author": "John"}
],
"stats_table": [
{"Product": "Widget Pro", "Sales": "1,234", "Rating": "4.8"},
{"Product": "Gadget X", "Sales": "567", "Rating": "4.5"}
]
},
"media": [],
"error": null
}
fill_form
Fill multiple form fields by label and optionally submit. Auto-detects field types (text, select, checkbox).
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| fields | object | yes | — | {label: value} pairs |
| submit | boolean | no | false | Auto-click submit button |
{
"fill_form": {
"fields": {
"Email": "user@example.com",
"Password": "secret",
"Country": "US",
"Remember me": true
},
"submit": true
}
}
upload
Upload files to a file input. Downloads from provided URLs and attaches them.
| Param | Type | Required | Description |
|---|---|---|---|
| ref / text / label | string | one required | Element targeting |
| files | array | yes | Array of file URLs to download and attach |
{"upload": {"ref": "e5", "files": ["https://example.com/doc.pdf"]}}
{"upload": {"label": "Resume", "files": ["https://example.com/resume.pdf"]}}
pdf
Generate a PDF of the current page. Base64 data in the media array with type: "pdf".
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| format | string | no | A4 | Paper size (A4, Letter, Legal) |
| landscape | boolean | no | false | Landscape orientation |
| print_background | boolean | no | true | Include backgrounds |
| scale | number | no | 1 | Scale factor (0.1–2) |
| margin | object | no | — | {top, right, bottom, left} in CSS units |
{"pdf": {}}
{"pdf": {"format": "A4", "landscape": true, "margin": {"top": "1cm", "bottom": "1cm"}}}
execute_js
Run custom JavaScript in the browser context. The return value is placed in the extraction object under the specified key.
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| code | string | yes | — | JavaScript code to execute. Use return to send values back |
| result_key | string | no | js_result | Key name in the extraction object for the return value |
| timeout | integer | no | 10000 | Maximum execution time in milliseconds |
// Get the page title
{"execute_js": {"code": "return document.title"}}
// Count elements on the page
{"execute_js": {"code": "return document.querySelectorAll('.product').length", "result_key": "product_count"}}
// Extract computed data
{"execute_js": {"code": "return { url: location.href, cookies: document.cookie, scrollY: window.scrollY }"}}
The code runs inside a new Function() wrapper, so use return to send values back. Execution is sandboxed within the page context — no access to Node.js APIs.
close
Destroy the session at the end of step execution. Must be the last step. Useful for fire-and-forget workflows.
{"close": {}}
Element Targeting
Steps that interact with elements (click, fill, type, select, check, scroll, upload) accept exactly one targeting param:
| Key | Example | Best for |
|---|---|---|
| ref | "ref": "e3" | Known elements from prior observe — most precise |
| text | "text": "Submit" | Buttons and links by visible text |
| label | "label": "Email" | Input fields by label/placeholder/aria-label |
Label matching checks (in order): aria-label, placeholder, associated <label>, name attribute, nearby text.
Response Format
Create and act calls return the same response structure.
| Field | Type | Description |
|---|---|---|
| session_id | string | Session identifier |
| expires_at | string | ISO 8601 expiry timestamp |
| request_id | string | Unique request ID for debugging (e.g. req_8f3a2b...) |
| completed | integer | Number of steps successfully executed |
| page | object|null | Current page state (see below) |
| page.url | string | Current URL |
| page.title | string | Page title |
| page.stable | boolean | true when the page passed stability checks (network idle 300ms + DOM mutations quiet 500ms). If false, the page is still loading — consider adding a wait step. |
| page.markdown | object | {content, length?} — page content as markdown |
| page.interactive_elements | array | Elements with refs and context: in (landmark), near (heading), form (parent form ref) |
| page.forms | array | Forms with element associations: {ref, id, action, method, fields: [refs]} |
| page.map | array|undefined | Lightweight section map: [{section, selector, hint}]. Auto-included on first observe; request again with include_page_map: true. Not included in mode: "full". |
| page.changes | object|null | Diff from previous observe. null on first. |
| page.scroll | object | {y, height, viewport, percent} |
| media | array | {type, format, data} objects. type: screenshot or pdf. |
| extraction | object|null | null when no extract steps used. Otherwise, extraction results. |
| blockers_dismissed | array | Auto-dismissed blockers (e.g. cookie_consent) |
| error | object|null | null on success. On failure: error details. |
Errors
All errors follow a consistent JSON format. On step failure, the response includes both the error and the current page state so you can decide what to do next:
{
"completed": 1,
"page": {
"url": "https://example.com",
"title": "Example Domain",
"interactive_elements": ["..."]
},
"error": {
"step": 1,
"action": "click",
"code": "element_not_found",
"message": "No visible element found matching text \"Submit\"",
"context": {}
}
}
HTTP errors
| Status | Code | When |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key |
| 404 | session_not_found | Session doesn't exist or expired |
| 429 | rate_limited | Too many requests |
| 429 | quota_exceeded | Credit quota exhausted |
| 503 | engine_unavailable | Browser engine temporarily down |
Step error codes
| Code | Description |
|---|---|
| element_not_found | No visible element matches the target |
| navigation_failed | Goto failed (timeout, DNS, SSL, etc.) |
| captcha_detected | CAPTCHA detected on page |
| captcha_unsolvable | Unsolvable bot/Vendor challenge (e.g. PerimeterX) — session is closed |
| access_denied | HTTP 403/451 or access blocked — session is closed |
| action_failed | Element detached, intercepted, etc. |
| extract_failed | Extraction step failed |
| invalid_request | Unknown step type or invalid params |
Recovery patterns
| Error | What to do |
|---|---|
| element_not_found | Re-observe the page. Try label instead of ref, or use text matching. The page may have changed. |
| navigation_failed | Check URL scheme (must include https://). Retry with a longer wait_timeout. May be a DNS or SSL issue. |
| captcha_detected | Take a screenshot for human review. Consider using a proxy or rotating IP addresses. |
| captcha_unsolvable | Do not retry the same target through automation. Use GET /v1/sessions/:id to read error_code / error_message. Choose a different URL, proxy, or flow. |
| access_denied | Site or geo blocks the session. Check HTTP status in the error, try another region/proxy, or a different page. |
| rate_limited | Read the Retry-After header. Use exponential backoff. Do not retry immediately. |
| quota_exceeded | Your plan's credits are exhausted. Top up or upgrade your plan, or wait for the next billing cycle. |
| engine_unavailable | Retry after Retry-After seconds. If 3+ consecutive failures, check the status page. |
| session_not_found | Session expired or was destroyed. Create a new session and replay your steps. |
Retry example
import time, requests
def call_with_retry(url, headers, json, max_retries=3):
for attempt in range(max_retries):
resp = requests.post(url, headers=headers, json=json)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
if resp.status_code == 503:
time.sleep(2 ** attempt)
continue
return resp
raise Exception(f"Failed after {max_retries} retries")
Rate Limits & Quotas
Usage is metered in credits. Runtime, proxy bandwidth, AI selector resolutions, and CAPTCHA solves all consume credits from a single pool. Limits depend on your plan.
Response headers
Every API response includes rate limit headers so you can track your quota:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Requests allowed per minute |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when window resets |
| X-Request-Id | Unique request ID for debugging |
| Retry-After | Seconds to wait (on 429/503 responses) |
Plans
| Plan | Credits / mo | Concurrent | Max Session | Rate Limit |
|---|---|---|---|---|
| Trial | 5,000 | 1 | 5 min | 60 req/min |
| Starter | 500,000 | 5 | 15 min | 600 req/min |
| Pro | 2,000,000 | 50 | 30 min | 1,200 req/min |
| Scale | 10,000,000 | 100 | 1 hour | 3,000 req/min |
See Billing & Credits for credit rates and how each service is metered.
Workflows
Common patterns showing how to combine sessions and steps.
Explore then act — 2 calls
Get page state first, then use element refs to interact.
{"url": "https://example.com"}
{
"steps": [
{"fill": {"ref": "e1", "value": "search query"}},
{"click": {"ref": "e3"}}
]
}
Login + screenshot — 1 call
Fill a login form, take a screenshot, destroy the session — all at once.
curl -X POST https://api.browserbeam.com/v1/sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/login",
"steps": [
{"fill_form": {"fields": {"Email": "user@test.com", "Password": "pass"}, "submit": true}},
{"wait": {"ms": 2000}},
{"screenshot": {"full_page": true}},
{"close": {}}
]
}'
from browserbeam import Browserbeam
client = Browserbeam(api_key="YOUR_API_KEY")
with client.sessions.create(url="https://example.com/login") as session:
session.fill_form({"Email": "user@test.com", "Password": "pass"}, submit=True)
session.wait(ms=2000)
session.screenshot(full_page=True)
import Browserbeam from "@browserbeam/sdk";
const client = new Browserbeam({ apiKey: "YOUR_API_KEY" });
const session = await client.sessions.create({ url: "https://example.com/login" });
await session.fillForm({ fields: { Email: "user@test.com", Password: "pass" }, submit: true });
await session.wait({ ms: 2000 });
await session.screenshot({ full_page: true });
await session.close();
require "browserbeam"
client = Browserbeam::Client.new(api_key: "YOUR_API_KEY")
client.sessions.create(url: "https://example.com/login") do |session|
session.fill_form({"Email" => "user@test.com", "Password" => "pass"}, submit: true)
session.wait(ms: 2000)
session.screenshot(full_page: true)
end
{
"url": "https://example.com/login",
"steps": [
{"fill_form": {"fields": {"Email": "user@test.com", "Password": "pass"}, "submit": true}},
{"wait": {"ms": 2000}},
{"screenshot": {"full_page": true}},
{"close": {}}
]
}
Full-page scrape — 1 call
Scroll through the entire page, collecting all lazy-loaded content.
{
"url": "https://example.com/long-article",
"steps": [{"scroll_collect": {"max_text_length": 50000}}]
}
Structured extraction — 1 call
Extract structured data and close the session in one request.
{
"url": "https://example.com/products",
"steps": [
{"extract": {"products": [{"_parent": ".product", "name": "h2 >> text", "price": ".price >> text", "url": "a >> href"}]}},
{"close": {}}
]
}
PDF generation — 1 call
Generate a PDF and close the session.
{
"url": "https://example.com/report",
"steps": [{"pdf": {"format": "A4", "landscape": true}}, {"close": {}}]
}
Search + extract — 2 calls
Navigate, perform a search, then extract results.
{
"url": "https://example.com",
"steps": [
{"fill": {"label": "Search", "value": "browser automation"}},
{"click": {"text": "Search"}}
]
}
{
"steps": [
{"extract": {"results": [{"_parent": ".result", "title": "h3 >> text", "url": "a >> href"}]}},
{"close": {}}
]
}
Building an LLM agent loop — N calls
The most common pattern for AI agents: create a session, observe, decide, act, repeat until done.
from browserbeam import Browserbeam
client = Browserbeam(api_key="YOUR_API_KEY")
# 1. Create session and navigate
session = client.sessions.create(url="https://example.com")
# 2. Agent loop: observe → decide → act
while True:
page = session.page
elements = page.interactive_elements
# 3. Send page state to your LLM to decide next action
next_steps = llm_decide(page.markdown.content, elements)
if next_steps is None:
break # Goal reached
# 4. Execute the steps
session.act(next_steps)
if session.error:
print(f"Step failed: {session.error.message}")
break
# 5. Clean up
client.sessions.destroy(session.session_id)
import Browserbeam from "@browserbeam/sdk";
const client = new Browserbeam({ apiKey: "YOUR_API_KEY" });
// 1. Create session and navigate
const session = await client.sessions.create({ url: "https://example.com" });
// 2. Agent loop: observe → decide → act
while (true) {
const { page } = session;
// 3. Send page state to your LLM to decide next action
const nextSteps = await llmDecide(page.markdown.content, page.interactive_elements);
if (!nextSteps) break; // Goal reached
// 4. Execute the steps
await session.act(nextSteps);
if (session.error) {
console.error(`Step failed: ${session.error.message}`);
break;
}
}
// 5. Clean up
await client.sessions.destroy(session.sessionId);
require "browserbeam"
client = Browserbeam::Client.new(api_key: "YOUR_API_KEY")
# 1. Create session and navigate
session = client.sessions.create(url: "https://example.com")
# 2. Agent loop: observe → decide → act
loop do
page = session.page
elements = page.interactive_elements
# 3. Send page state to your LLM to decide next action
next_steps = llm_decide(page.markdown.content, elements)
break unless next_steps # Goal reached
# 4. Execute the steps
session.act(next_steps)
if session.error
puts "Step failed: #{session.error.message}"
break
end
end
# 5. Clean up
client.sessions.destroy(session.session_id)
The key insight: every response includes fresh page state via auto-observe, so your LLM always has up-to-date content and element refs to decide its next action.