AI agent (Pilot)
Pilot is the hosted agent that drives a BrowserView session from a plain-language task. It sees the page, plans a step, acts, and reports back — streaming its reasoning, actions, and answer as events you can render or automate against. Available on paid plans; billed in agent credits.
How it works
Create a session with agent: true and its container carries an agent runtime beside the browser. Each message you send starts a turn: the agent reads the page, reasons, performs browser actions one step at a time (navigate, click, type, scroll, extract, wait…), and ends with an answer, a question for you, or a limit. Everything it does is visible live in the viewer and recorded in an ordered event log, so a dropped connection never loses the transcript.
curl -X POST https://sessions.browserview.io/sessions \ -H "authorization: Bearer $BROWSERVIEW_API_KEY" \ -H "content-type: application/json" \ -d '{"start_url": "https://news.ycombinator.com", "agent": true}'- The agent is provisioned at create time. Sessions created without
agent: truereturn409from the agent endpoints. - Humans can take over in the viewer at any time; the agent resumes from whatever state the page is in when you reply.
- On the free plan the field is ignored — the agent is a paid feature.
In the console
Every session page has a Chat panel beside the live viewer. Type a task, pick a model (grouped by provider, with a cost tier glyph) and an effort level (Fast / Balanced / Thorough), and send. The transcript shows the agent's thinking, a step-by-step timeline with status and timing, its answer, and a footer with model, duration, and credits. When it asks a question, answer with a chip or a reply — or take over in the viewer and reply when you're done. Escape or the stop button interrupts a run; sending while it runs adds an instruction to the current turn.
Turns are kept after the session ends: the History page has an Agent tab, and Billing lists your recent turns with their credit cost.
API
All agent endpoints live under the session at https://sessions.browserview.io. Authenticate with your API key, or with a session token minted with scope agent.
| Method | Path | Purpose |
|---|---|---|
POST | /sessions/{id}/agent/messages | Start a turn (SSE), or steer the running one |
GET | /sessions/{id}/agent | Conversation state and usage totals |
GET | /sessions/{id}/agent/events | Replay or tail the event log |
POST | /sessions/{id}/agent/reply | Answer an ask_user question (starts a new turn) |
POST | /sessions/{id}/agent/interrupt | Stop the running turn |
DELETE | /sessions/{id}/agent | Reset the conversation (204) |
GET | /agent/models | Available models, providers, and list prices |
Send a message
{ "content": "Find the cheapest flight to Lisbon next Friday", // 1–32,000 chars "model": "claude-sonnet-5", // optional; see GET /agent/models "effort": "medium", // low | medium | high (Fast / Balanced / Thorough) "max_steps": 25, // optional cap on browser actions this turn "budget_credits": 100, // optional per-turn credit ceiling "allowed_domains": ["*.example.com"], // optional navigation allow-list "stream": true // false → 202 {turn_id, seq, steering}}With Accept: text/event-stream (the default) the response is a stream that starts with status and ends after turn.end. Sending another message while a turn is running steers it — the text is appended to the agent's context and echoed back as message.user{steering: true}.
curl -N https://sessions.browserview.io/sessions/$SESSION_ID/agent/messages \ -H "authorization: Bearer $BROWSERVIEW_API_KEY" \ -H "accept: text/event-stream" \ -H "content-type: application/json" \ -d '{"content": "Summarize the top 3 stories", "effort": "medium", "max_steps": 20}' event: statusid: 0data: {"seq":0,"ts":1756000000000,"type":"status","state":"running"} event: turn.startid: 1data: {"seq":1,"type":"turn.start","turn_id":"t_8f2a","model":"claude-sonnet-5","effort":"medium","max_steps":20} event: step.startid: 4data: {"seq":4,"type":"step.start","step_id":"s_1","index":0,"action":{"kind":"click","id":12},"description":"Open the first story"} event: step.endid: 5data: {"seq":5,"type":"step.end","step_id":"s_1","ok":true,"duration_ms":412,"url":"https://…"} event: text.deltaid: 9data: {"seq":9,"type":"text.delta","text":"1. **Foo** — "} event: turn.endid: 14data: {"seq":14,"type":"turn.end","turn_id":"t_8f2a","stop_reason":"end_turn","success":true,"steps":3,"cost_usd":0.041,"duration_ms":12800,"llm_calls":4}402withdeny_reason(plan_free,credits_exhausted,agent_credit_cap,suspended) when billing refuses the turn.409when the session has no agent, or when it is waiting on a question — answer with/replyinstead.503when the agent service is temporarily unavailable; retry with backoff.
State, replies, and reconnecting
curl https://sessions.browserview.io/sessions/$SESSION_ID/agent \ -H "authorization: Bearer $BROWSERVIEW_API_KEY" { "status": "needs_input", // disabled | idle | running | needs_input "model": "claude-sonnet-5", "turn_count": 2, "usage_totals": { "input_tokens": 41200, "output_tokens": 1830, "cost_usd": 0.19 }, "pending_question": { "question": "Which account should I use?", "options": ["Work", "Personal"] }, "current_turn_id": null, "last_seq": 57}curl -N https://sessions.browserview.io/sessions/$SESSION_ID/agent/reply \ -H "authorization: Bearer $BROWSERVIEW_API_KEY" \ -H "accept: text/event-stream" \ -H "content-type: application/json" \ -d '{"content": "Work"}'Every event carries a monotonic seq. To rebuild a transcript, fetch GET …/agent/events?after=-1 (JSON). To reattach to a live turn, tail …/agent/events?after=<last_seq>&follow=1 as SSE — the server replays anything you missed, then streams live.
# Let a browser or a less-trusted worker drive the agent without your API keycurl -X POST https://sessions.browserview.io/sessions/$SESSION_ID/tokens \ -H "authorization: Bearer $BROWSERVIEW_API_KEY" \ -H "content-type: application/json" \ -d '{"scope": "agent", "ttl_seconds": 3600}' # then send x-session-token: <token> on the /agent endpointsEvents
Server-sent events are framed as event: <type>, id: <seq>, data: <json>, with a : ping comment every 15 seconds. Every payload includes seq, ts (epoch ms), type, and turn_id.
| Event | Fields | Meaning |
|---|---|---|
status | state | First event on subscribe: idle | running | needs_input |
turn.start | turn_id, message_id, model, effort, max_steps | A turn began |
message.user | message_id, content, steering | Your message, as the agent received it |
reasoning.delta | text | Streamed thinking (followed by reasoning.end) |
text.delta | text | Streamed answer text (Markdown) |
step.start | step_id, index, action{kind,…}, description, reason | A browser action is about to run |
step.end | step_id, ok, error?, duration_ms, url, title | The action finished |
ask_user | question, options? | The agent needs input; the turn ends with needs_input |
usage | model, provider, *_tokens, cost_usd, turn_cost_usd, conversation_cost_usd | After every LLM call |
turn.end | stop_reason, success?, answer?, steps, cost_usd, duration_ms, usage, llm_calls | The turn ended |
error | code, message, retryable | Something failed; terminal when no turn is open |
stop_reason is one of end_turn, needs_input, max_steps, budget_exceeded, interrupted, or error. The answer field on turn.end is the complete final text, so clients that ignore deltas still get the result.
Credits
Agent usage is billed in credits: 1 credit = $0.01. A turn's billed value is the provider list price of the tokens it used, plus 40%, rounded up to the next credit (a turn that made an LLM call costs at least 1 credit). Starter includes 500 credits per billing period, Pro includes 3,000. Beyond the pool, credits meter onto your subscription at $0.01 each, up to a per-period cap you control from Billing (default 2,000; set 0 to stop at the included pool).
- Model prices are on
GET /agent/models; the console shows each model's cost tier as · / ·· / ···. - A single turn can never spend more than 300 credits; set
budget_creditsfor a tighter ceiling. When it is hit the turn ends withbudget_exceeded. - Credits are charged when the turn ends, from the
usagetotals — steering or stopping a turn only bills what it actually used.
Limits
- One turn at a time per session; a second message while running steers rather than queues.
max_stepsdefaults to 25 (max 100). Reaching it ends the turn withmax_steps; send “continue” to keep going.- Messages are limited to 32,000 characters. Agent POSTs share the per-owner rate bucket;
429carriesRetry-After. allowed_domainsrestricts navigation for the turn; a blocked navigation shows up as a failed step.
Webhook
Subscribe to agent.turn.completed in the console's webhook settings to be notified after every turn. Deliveries are signed like every other event (see Webhooks).
{ "type": "agent.turn.completed", "data": { "session_id": "3f9c62d81b4a", "turn_id": "t_8f2a", "model": "claude-sonnet-5", "stop_reason": "end_turn", "steps": 3, "cost_usd": 0.041, "credits": 6, "started_at_ms": 1756000000000, "ended_at_ms": 1756000012800 }}Local libraries
Prefer to run the agent loop yourself, with your own model keys? The open-source pilot libraries wrap a BrowserView session in the same act / extract / observe / run primitives, executed from your process over CDP. They bill browser-hours only — no credits.
Python
import pilot async with pilot.launch(start_url="https://example.com") as page: # or pilot.attach(session_id) print(page.viewer_url) # watch live await page.act("click the login link") # one action data = await page.extract("the product name and price") # JSON result = await page.run("add the cheapest item to the cart", max_steps=20) print(result.success, result.answer, len(result.steps))TypeScript
import { launch } from "@browserview/pilot"; const page = await launch({ startUrl: "https://example.com" }); // or attach(sessionId)await page.act("open the pricing page");const plans = await page.extract("every plan name and monthly price");const result = await page.run("find the enterprise contact email", { maxSteps: 20 });await page.close();Both read BROWSERVIEW_API_KEY plus your model provider key from the environment. See Agent integrations for the full method tables.