---
title: Browser agent
description: Send a goal and a start URL; an AI agent does the task in a real Chromium browser and returns an answer and structured JSON.
---

The browser agent does web tasks your code can't do with a plain HTTP request: search a site, click through pages, fill and submit forms, log in, and read what the page shows. You describe the task in plain words. The agent works in a real Chromium browser, one action at a time, and returns a text `answer` and, if you ask for it, JSON in `data`.

It uses the same API key and prepaid balance as the rest of Lovely Router. You pay per step, from $0.006, and the models are included. There is no run fee.

:::cards
- :sparkle: [Modes, models & pricing](/docs/browser-agent/modes) Fast or accurate, your own model, what a run costs.
- :stream: [Live events & screen](/docs/browser-agent/live) Stream each step and the browser's screen as the run works.
- :code: [Structured output](/docs/browser-agent/structured-output) Get JSON in your schema in `data`.
- :hand: [Human in the loop](/docs/browser-agent/human-in-the-loop) Questions, approvals, CAPTCHAs and takeovers.
- :lock: [Logins, secrets & profiles](/docs/browser-agent/logins) Sign in without the model ever seeing a password.
- :file: [File uploads](/docs/browser-agent/files) Let the agent attach the right file to a form.
- :webhook: [Webhooks](/docs/browser-agent/webhooks) Get notified when a run waits for you or finishes.
:::

## Run your first task

:::steps
### Get an API key

Create a key in [API keys](/app/keys) and export it. A post about Lovely Router on X gets you $2 of free credit, enough for dozens of short runs ([how](/docs/account/billing#free-credit)).

```bash
export LOVELY_API_KEY="lr-..."
```

### Start a run

Send a `goal` and a `start_url` to `POST /v1/browser/runs`. The response comes back at once with status `202` and the run's `id`. The agent keeps working in the background.

```bash title="cURL"
curl https://lovelyrouter.com/v1/browser/runs \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "goal": "Find the price of the book \"A Light in the Attic\" and whether it is in stock.",
    "start_url": "https://books.toscrape.com/"
  }'
```

```python title="Python"
import os
import requests

API = "https://lovelyrouter.com/v1/browser"
H = {"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"}

run = requests.post(f"{API}/runs", headers=H, json={
    "goal": 'Find the price of the book "A Light in the Attic" and whether it is in stock.',
    "start_url": "https://books.toscrape.com/",
}).json()
print(run["id"], run["status"])
```

```js title="Node.js"
const API = 'https://lovelyrouter.com/v1/browser';
const headers = {
  Authorization: `Bearer ${process.env.LOVELY_API_KEY}`,
  'content-type': 'application/json',
};

let run = await fetch(`${API}/runs`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    goal: 'Find the price of the book "A Light in the Attic" and whether it is in stock.',
    start_url: 'https://books.toscrape.com/',
  }),
}).then((r) => r.json());
console.log(run.id, run.status);
```

### Wait for the result

Fetch the run with `?wait=30`. The request holds for up to 30 seconds (50 at most) and returns as soon as the status changes, so a loop of these calls follows the run without busy polling.

To watch the run instead, with each step and the browser's screen as they happen, add `"stream": true` when you start it. See [Live events & screen](/docs/browser-agent/live).

```bash title="cURL"
curl "https://lovelyrouter.com/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d?wait=30" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

```python title="Python"
while run["status"] == "running":
    run = requests.get(f"{API}/runs/{run['id']}", headers=H, params={"wait": 30}).json()

print(run["status"], run["answer"])
```

```js title="Node.js"
while (run.status === 'running') {
  run = await fetch(`${API}/runs/${run.id}?wait=30`, { headers }).then((r) => r.json());
}
console.log(run.status, run.answer);
```

### Read the result

A finished run has a `status`, the agent's `answer`, and what it cost in `charge_usd` (abridged):

```json
{
  "id": "brun_3f9a1c7e0b2d4e6f8a9b0c1d",
  "object": "browser_run",
  "status": "succeeded",
  "mode": "fast",
  "steps": 2,
  "max_steps": 50,
  "current_url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "answer": "\"A Light in the Attic\" costs £51.77 and is in stock (22 available).",
  "data": null,
  "error": null,
  "charge_usd": 0.012,
  "reserved_usd": 0
}
```

The full list of fields is in [the run object](/docs/api/browser#the-run-object). Add `&trace=1` to the request to get every step the agent took.
:::

You can also start runs from the [console](/app/browser): pick an example or write a task, watch the browser's screen and each step live, answer the agent when it asks, and copy the matching cURL, Python or JavaScript code.

## How a run works

A run starts as `running` and ends in one of four final statuses. In between, it can pause as `waiting_for_human` and continue after you reply.

| Status | Meaning |
| --- | --- |
| `running` | The agent is working. |
| `waiting_for_human` | The agent needs you: an answer, an approval, or a person in the browser. `needs` says what. See [Human in the loop](/docs/browser-agent/human-in-the-loop). |
| `succeeded` | The agent finished and wrote its `answer`. |
| `failed` | The run ended without finishing. `error` says why, for example `reached the step limit (50)` or `blocked by the website`. |
| `timed_out` | Nobody replied within `human_timeout_minutes`. |
| `stopped` | You stopped it with `POST /v1/browser/runs/{id}/stop`. |

Each step, the agent reads the current page, picks one action, and does it. It stops when the goal is met, when it needs a person, or when it reaches `max_steps`. Before it clicks a button that submits a form, it checks every field against your goal and fixes values that don't match.

`succeeded` means the agent finished, not that the website accepted what it did. For tasks that submit something, read the `answer`: if the page showed a validation error or no confirmation, the answer says so.

When the run ends, the unused part of the amount reserved at the start returns to your balance and `charge_usd` shows the final charge. `charge_usd` is `null` until then.

## What counts as a step

A step is one browser action:

- click a link, button, checkbox or list option
- type into a field, or type and press Enter
- choose an option in a dropdown
- fill a saved secret or upload a file
- go back to the previous page
- wait for a page that has not loaded yet, or load it again
- run read-only code to read data from the page (accurate mode only)
- write the final answer

Pauses for a person (a question, an approval, a handover) appear in the trace but are not billed. A step that fails, for example because the element moved, still counts. The run's `steps` field counts every recorded step, pauses included.

A simple lookup takes 1 to 3 steps. A login takes 4 to 8. A long multi-page form takes 15 to 25. [Modes, models & pricing](/docs/browser-agent/modes) has cost examples.

## Write goals that work

The goal is the agent's only instruction. Write it the way you would brief a careful assistant who has never seen the site.

- **Include every value the task needs.** Names, emails, dates, quantities, the exact product. Don't leave the agent to guess: if a value is missing, it stops to ask you (unless you turned questions off).
- **Say what to report.** "Tell me the price and whether it is in stock" gets a better answer than "check the book".
- **For actions, say what proves success.** "Submit the form and tell me the confirmation number."
- **Start close to the task.** Use the search page or the form itself as `start_url`, not the home page, when you know it.
- **Name the place to look** when the site has several: "in the Reviews tab", "using the site's own search".
- **One task per run.** Split unrelated tasks into separate runs.
- **Keep secrets out of the goal.** Pass passwords as [vault secrets](/docs/browser-agent/logins), never in the goal text.

| Instead of | Write |
| --- | --- |
| Find cheap flights to New York. | Search one-way flights from SFO to JFK on November 3, 2026 for 1 adult. Tell me the cheapest fare, the airline and the departure time. |
| Sign me up for the newsletter. | Subscribe ada@example.com to the weekly newsletter in the page footer and tell me the confirmation message. |
| Get the stories. | Get the top 5 stories on the front page with their title, link and points. (Add an `output_schema` for JSON.) |
| Log in with ada / hunter22 and download my invoice. | Log in and tell me the total of the latest invoice. Pass `"secrets": ["shop_user", "shop_password"]`. |

## Limits

| Limit | Value |
| --- | --- |
| `goal` length | 4,000 characters |
| `start_url` | An `http` or `https` URL |
| `max_steps` | 1 to 100, default 50 |
| `human_timeout_minutes` | 1 to 1,440, default 30 |
| `?wait=` on a status request | Up to 50 seconds |
| Screen frames in a stream | About 4 a second at most |
| `output_schema` size | 20 KB |
| Runs at once | 10 per account, shared with [data tools](/docs/tools) |
| Live View link for a handover | Valid for 1 hour |
| Files | 10 MB each, 100 per account |
| Secret value | 4 KB |

When you reach the limit on runs at once, `POST /v1/browser/runs` returns `429`. When your balance is below what the run reserves, it returns `402`. [Errors](/docs/api/browser#errors) lists every error.

## Use it from an AI agent

The [MCP server](/docs/tools/mcp) at `https://lovelyrouter.com/mcp` exposes the browser agent as four tools: `browser_run`, `browser_status`, `browser_reply` and `browser_takeover`. They use the same key, limits and prices as the REST API. The MCP tools take vault secret names, never secret values: store secrets in the vault first.

## Next steps

:::cards
- :sparkle: [Modes, models & pricing](/docs/browser-agent/modes) Choose a mode and see what a run costs.
- :code: [Structured output](/docs/browser-agent/structured-output) Turn what the agent reads into JSON.
- :hand: [Human in the loop](/docs/browser-agent/human-in-the-loop) Handle questions, approvals and CAPTCHAs.
- :book: [API reference](/docs/api/browser) Every endpoint, parameter and field.
:::
