Docs / AI agent

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.

Create a session with the agent
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: true return 409 from 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.

MethodPathPurpose
POST/sessions/{id}/agent/messagesStart a turn (SSE), or steer the running one
GET/sessions/{id}/agentConversation state and usage totals
GET/sessions/{id}/agent/eventsReplay or tail the event log
POST/sessions/{id}/agent/replyAnswer an ask_user question (starts a new turn)
POST/sessions/{id}/agent/interruptStop the running turn
DELETE/sessions/{id}/agentReset the conversation (204)
GET/agent/modelsAvailable models, providers, and list prices

Send a message

POST /sessions/{id}/agent/messages
{  "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}
  • 402 with deny_reason (plan_free, credits_exhausted, agent_credit_cap, suspended) when billing refuses the turn.
  • 409 when the session has no agent, or when it is waiting on a question — answer with /reply instead.
  • 503 when the agent service is temporarily unavailable; retry with backoff.

State, replies, and reconnecting

GET /sessions/{id}/agent
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}
POST /sessions/{id}/agent/reply
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.

Agent-scoped tokens
# 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 endpoints

Events

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.

EventFieldsMeaning
statusstateFirst event on subscribe: idle | running | needs_input
turn.startturn_id, message_id, model, effort, max_stepsA turn began
message.usermessage_id, content, steeringYour message, as the agent received it
reasoning.deltatextStreamed thinking (followed by reasoning.end)
text.deltatextStreamed answer text (Markdown)
step.startstep_id, index, action{kind,…}, description, reasonA browser action is about to run
step.endstep_id, ok, error?, duration_ms, url, titleThe action finished
ask_userquestion, options?The agent needs input; the turn ends with needs_input
usagemodel, provider, *_tokens, cost_usd, turn_cost_usd, conversation_cost_usdAfter every LLM call
turn.endstop_reason, success?, answer?, steps, cost_usd, duration_ms, usage, llm_callsThe turn ended
errorcode, message, retryableSomething 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_credits for a tighter ceiling. When it is hit the turn ends with budget_exceeded.
  • Credits are charged when the turn ends, from the usage totals — 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_steps defaults to 25 (max 100). Reaching it ends the turn with max_steps; send “continue” to keep going.
  • Messages are limited to 32,000 characters. Agent POSTs share the per-owner rate bucket; 429 carries Retry-After.
  • allowed_domains restricts 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

pip install browserview-pilot
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

npm install @browserview/pilot
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.