Skip to content

Browser Actions API

All routes are under /browser/{sid} where {sid} is the session ID.


POST /browser/{sid}/navigate
{"url": "https://example.com", "timeout": 30000}

Response:

{
"url": "https://example.com/",
"status": 200,
"snapshot": "h1 \"Example Domain\"\n...",
"truncated": false
}

POST /browser/{sid}/wait
{"selector": ".results", "load": false, "timeout": 30000}

Pass "selector" to wait for an element, or "load": true to wait for the page’s load event (not network-idle — use navigate’s wait: "networkidle" for that).

Response: {"ok": true}


GET /browser/{sid}/url

Response: {"url": "https://example.com/current-page"}


GET /browser/{sid}/snapshot?search=<regex>&locator=<selector>

Response:

{
"tree": "navigation\n [1] a \"Home\"...",
"truncated": false,
"hint": "Page has 600+ nodes. Use search param to filter."
}

POST /browser/{sid}/screenshot
{
"full": false,
"path": "/tmp/page.png",
"width": null,
"scale": null,
"quality": "medium"
}

quality is a preset: "low" (400px), "medium" (800px), "high" (1200px). Overrides width when set.

Response: {"path": "/Users/you/.brow/screenshots/2024-01-01T12-00-00.png"}


GET /browser/{sid}/html?locator=<selector>&search=<regex>

Response: {"html": "<main>...</main>"}


GET /browser/{sid}/logs?search=<str>&count=50

Response: {"logs": "[log] Page loaded\n[error] Failed to fetch..."}


GET /browser/{sid}/network?search=<str>&count=50&include_static=false&include_response=false

Response: {"network": "GET 200 application/json https://api.example.com/data\n → {\"results\":..."}

DELETE /browser/{sid}/network

Response: {"ok": true}


POST /browser/{sid}/fetch
{
"url": "https://api.example.com/data",
"method": "GET",
"headers": {"Accept": "application/json"},
"body": null,
"no_cookies": false
}

When no_cookies is false (default), the request runs inside the browser page using fetch() — cookies and session are included. When true, a plain httpx request is made without any browser state.

Response:

{
"status": 200,
"contentType": "application/json",
"body": "{\"results\": [...]}"
}

GET /browser/{sid}/websocket?search=<str>&count=50

Response: {"websocket": "recv wss://example.com/ws {\"op\":\"replace\",\"path\":\"/price\",\"value\":1.85}\n..."}

DELETE /browser/{sid}/websocket

Response: {"ok": true}


POST /browser/{sid}/click
{
"selector": "button[type=submit]",
"ref": null,
"timeout": 30000,
"retry": 0,
"wait_for_selector": true
}

Provide either selector or ref (element ref from snapshot). retry attempts the click up to N additional times (1-second delay each) if it fails.

Response:

{"ok": true, "snapshot": "...", "truncated": false}

Repeat a click until a selector clears, or a safety cap is hit — pagination, “load more”, batch-dismiss actions.

POST /browser/{sid}/click-until
{
"selector": "button.load-more",
"until_gone": "button.load-more",
"max_iterations": 25,
"settle_ms": 500,
"timeout": 30000
}

settle_ms is a pause after each click for the page to refill before the next until_gone check.

Response:

{"iterations": 4, "done": true, "reason": "until_gone cleared"}

done: false means the cap was hit (or the selector never appeared) with work possibly still remaining — always check reason, since a capped sweep looks identical to a finished one until you read it.


POST /browser/{sid}/fill
{
"selector": "#email",
"ref": null,
"value": "user@example.com",
"timeout": 30000,
"retry": 0,
"wait_for_selector": true
}

Response: {"ok": true, "snapshot": "..."}


POST /browser/{sid}/select
{"selector": "#country", "ref": null, "value": "Slovakia", "timeout": 30000}

Response: {"ok": true, "snapshot": "..."}


POST /browser/{sid}/type
{"text": "Hello, world"}

Types at the current focus position using real keyboard events.

Response: {"ok": true}


POST /browser/{sid}/key
{"key": "Enter"}

Accepts any Playwright key string: Enter, Tab, Escape, Meta+a, Control+c, ArrowDown, etc.

Response: {"ok": true, "snapshot": "..."}


POST /browser/{sid}/hover
{"selector": "nav .dropdown", "timeout": 30000}

Response: {"ok": true}


POST /browser/{sid}/scroll
{"pixels": 1000}

Or scroll to an element:

{"selector": "#footer"}

Response: {"ok": true}


POST /browser/{sid}/drag
{"source": ".draggable-card", "target": ".drop-zone"}

Response: {"ok": true}


POST /browser/{sid}/upload
{"selector": "input[type='file']", "filepath": "/path/to/file.csv"}

Response: {"ok": true}


GET /browser/{sid}/actions?as_json=false

Returns the action log as formatted text (default) or JSON array.

Response (text):

{"actions": "1 navigate https://example.com [200]\n2 click button[type=submit]\n..."}

Response (JSON):

{
"actions": [
{"seq": 1, "action": "navigate", "url": "https://example.com", "status": 200},
{"seq": 2, "action": "click", "selector": "button[type=submit]"}
]
}
DELETE /browser/{sid}/actions

Response: {"ok": true}


POST /browser/{sid}/replay
{
"playbook": {
"name": "my-playbook",
"base_url": "https://example.com",
"auth": "none",
"stop_on_failure": false,
"vars": {"query": "test"},
"steps": [
{"action": "navigate", "url": "/search"},
{"action": "fill", "selector": "input", "value": "{query}"},
{"action": "key", "key": "Enter"},
{"action": "wait", "selector": "#results", "state": "visible", "timeout": 10000},
{"action": "assert", "selector": ".result-count"},
{
"action": "fetch",
"url": "/api/results?q={query}",
"headers": {"X-Api-Key": "{api_key}"},
"output": "results"
},
{
"action": "for_each",
"var": "item",
"items": "results",
"steps": [{"action": "navigate", "url": "/item/{item[id]}"}]
}
]
},
"vars": {"query": "override"}
}

Variables in vars override those in playbook.vars. A top-level auth: "none" applies to every fetch step that doesn’t set its own auth. stop_on_failure: true halts the whole run — including out of a for_each — after the first failed step, instead of the default “record it and keep going.”

Supported actions: navigate, click, fill, key, select, fetch (method, headers, auth, output — captured JSON becomes {name} and {name[key]} in later steps, expect_status to override the default ok = status < 400), wait (selector+state, or ms for a fixed sleep), assert (same shape as wait, but a non-match fails the step), and for_each (loop nested steps over a literal or captured list).

Response:

{
"results": [
{"action": "navigate", "ok": true, "url": "https://example.com/search", "status": 200},
{"action": "fill", "ok": true},
{"action": "key", "ok": true},
{"action": "wait", "ok": true},
{"action": "assert", "ok": true},
{"action": "fetch", "ok": true, "url": "...", "status": 200, "data": {...}}
]
}