---
title: Browser endpoints
description: Every /v1/browser endpoint for runs, vault secrets, files and profiles, with parameters, limits, responses and errors.
layout: reference
---

The browser agent API lives under `https://lovelyrouter.com/v1/browser`. Authenticate every request with your API key as `Authorization: Bearer $LOVELY_API_KEY` ([Authentication](/docs/authentication)). Request and response bodies are JSON, except file uploads and screenshots. Runs are billed from your prepaid balance; see [Modes, models & pricing](/docs/browser-agent/modes).

For a walkthrough, start with the [browser agent overview](/docs/browser-agent).

## The run object

A run is one task for the agent. Every run endpoint returns this object. Fields marked *API only* are missing from [webhook](/docs/browser-agent/webhooks) payloads.

:::params Attributes
- `id` string
  Unique id, `brun_` followed by 24 characters.
- `object` string
  Always `browser_run`.
- `status` string
  `running`, `waiting_for_human`, `succeeded`, `failed`, `timed_out` or `stopped`.
- `goal` string
  The goal you sent.
- `start_url` string
  The URL the run started on.
- `engine` string
  Always `chromium`.
- `mode` string
  `fast` or `accurate`.
- `model` string | null
  The model you chose with `model`, or `null` for the included models.
- `model_usd` number
  Only when you chose a `model`: what its tokens have cost so far, including the 20% surcharge.
- `steps` integer
  Steps recorded so far, including pauses for a person. Pauses are not billed.
- `max_steps` integer
  The run's step limit.
- `billable_time_s` integer
  Seconds beyond each step's and each pause's included minute, so far.
- `time_usd` number
  The cost of `billable_time_s`, at $0.005 per minute.
- `current_url` string
  The page the browser is on, or was on when the run ended.
- `answer` string | null
  The agent's answer once it finishes. Runs that reach `max_steps` also get an answer from the last page.
- `data` object | array | null
  Structured output once the run ends, when you passed `output_schema` or the goal asked for a structure. See [Structured output](/docs/browser-agent/structured-output).
- `data_error` string
  Only present when structured output failed, with the reason.
- `error` string | null
  Why the run did not succeed, e.g. `reached the step limit (50)`, `blocked by the website`, `no human response within 30 minutes`, `stopped by the user`.
- `needs` object | null
  What the run is waiting for, when `status` is `waiting_for_human`. Otherwise `null`.
  - `kind` string
    `question`, `approval` or `takeover`.
  - `prompt` string
    What the agent needs, in one sentence.
  - `expects_secret` boolean
    Questions only. `true` when the question asks for a code, password or PIN: reply with `secret`.
  - `fields` array
    Approvals only. The form's current values, e.g. `Email: "ada@example.com"`.
  - `action` string
    Approvals only. The label of the button the agent wants to click.
  - `live_view_url` string
    Takeovers only. A link that shows the browser live and lets a person use it.
  - `live_view_expires_at` string
    Takeovers only. When `live_view_url` stops working, one hour after the pause began.
  - `screenshot` string
    Path of a JPEG of the page. See [Get a screenshot](#get-a-screenshot).
  - `since` string
    When the pause began (ISO 8601).
  - `reply` string
    Path to post your reply to.
- `profile_saved` object | null
  With `save_profile`, the profile saved when the run ended: `id`, `name` and `cookies` (the number of cookies saved).
- `usage` object
  `decision_calls` and `llm_calls`: how many model calls the agent made. Informational; included in the step price.
- `created_at` string
  When the run started (ISO 8601).
- `finished_at` string | null
  When the run ended.
- `trace` array
  Only with `?trace=1`. One entry per step: `step`, `url`, `action` (such as `click:e12`, `type:e3`, `choose:e5`, `secret:e4:shop_password`, `upload:e9`, `back`, `done`, or `ask`, `approval`, `handoff` for pauses), `description`, `ms` (duration) and `error` (`null`, or why the step failed).
- `charge_usd` number | null
  *API only.* What the run cost, once it is settled a few seconds after it ends. `null` before.
- `reserved_usd` number
  *API only.* The amount held while the run is live; `0` once it is settled.
:::

```json title="The run object" aside
{
  "id": "brun_3f9a1c7e0b2d4e6f8a9b0c1d",
  "object": "browser_run",
  "status": "succeeded",
  "goal": "Find the price of the book \"A Light in the Attic\" and whether it is in stock.",
  "start_url": "https://books.toscrape.com/",
  "engine": "chromium",
  "mode": "fast",
  "model": null,
  "steps": 2,
  "max_steps": 50,
  "billable_time_s": 0,
  "time_usd": 0,
  "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,
  "needs": null,
  "profile_saved": null,
  "usage": { "decision_calls": 3, "llm_calls": 1 },
  "created_at": "2026-10-11T09:30:00.000Z",
  "finished_at": "2026-10-11T09:30:07.000Z",
  "trace": [
    {
      "step": 1,
      "url": "https://books.toscrape.com/",
      "action": "click:e54",
      "description": "click link \"A Light in the ...\" → https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
      "ms": 2140,
      "error": null
    },
    {
      "step": 2,
      "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
      "action": "done",
      "description": "Read the result and wrote the answer",
      "ms": 2875,
      "error": null
    }
  ],
  "charge_usd": 0.012,
  "reserved_usd": 0
}
```

## Create a run

::endpoint POST /v1/browser/runs

Starts a run and returns `202` with the run object at once. The agent works in the background; follow it with [its event stream](#stream-a-run), [Retrieve a run](#retrieve-a-run) or a [webhook](/docs/browser-agent/webhooks). Starting a run reserves its maximum cost from your balance ([how the hold works](/docs/browser-agent/modes#how-the-hold-works)).

The response also has `poll`, a ready-made status path with `?wait=30`, `events`, the run's [event stream](#stream-a-run), and `webhook_secret` when you passed `webhook`. `webhook_secret` is returned only here (or in the first event of a streamed start).

With `"stream": true`, the response is instead `200` with the run's [event stream](#stream-a-run), from the start until the run ends. See [Live events & screen](/docs/browser-agent/live).

:::params Body parameters
- `goal` string, required
  What to do, in plain words, with every value the task needs. Up to 4,000 characters. See [Write goals that work](/docs/browser-agent#write-goals-that-work).
- `start_url` string, required
  The first page to open. An `http` or `https` URL.
- `mode` string, default `fast`
  `fast` ($0.006 per step) or `accurate` ($0.012 per step: also looks at screenshots when unsure and can run read-only page code). See [Modes](/docs/browser-agent/modes).
- `max_steps` integer, default `50`
  Step limit, 1 to 100. Values outside the range are clamped. The run is billed only for the steps it takes.
- `output_schema` object
  JSON Schema (draft 2020-12), or shorthand like `{"title": "string", "items": [{"name": "string"}]}`, for JSON in `data`. Up to 20 KB.
- `approve` string, default `risky`
  When to pause for approval before an action that submits something (a submit button, a buy/book/pay/send/delete button, or Enter in a non-search form): `risky` (only purchases, orders, sending, deleting, applications), `submit` (every such action) or `none`. Any other value returns `400`.
- `human` boolean, default `true`
  Whether the agent may ask questions and hand the browser over. Set `false` for unattended runs.
- `human_timeout_minutes` number, default `30` (`5` for a run started by a cloud agent)
  How long each pause waits for a reply, 1 to 1,440 minutes. Values outside the range are clamped. Then the run ends as `timed_out`.
- `secrets` array
  Names of [vault secrets](#store-a-secret) the agent may type.
- `ephemeral_secrets` object
  Secrets for this run only, deleted when it ends. A map from name (same rules as vault names) to:
  - `value` string, required
    The secret.
  - `kind` string, default `password`
    `password`, `username`, `text` or `totp`.
  - `origins` array, required
    1 to 20 sites where it may be typed, like `https://example.com` or `https://*.example.com`.
- `files` array | string
  Names or ids of [uploaded files](#upload-a-file) the agent may attach, or `"*"` for all of them.
- `profile` string
  Name or id of a saved [profile](#list-profiles) to start from.
- `save_profile` string
  Save the session's cookies and local storage under this name when the run ends. Up to 64 characters.
- `allowed_domains` array
  Hostnames the browser may visit, like `"example.com"` or `"*.example.com"`. Up to 50.
- `webhook` string
  An `https` URL that receives `run.waiting` and `run.finished` events.
- `model` string
  A model id from [`GET /v1/models`](/docs/api/models), e.g. `claude-sonnet-5.5`, for the agent's thinking, writing and extraction. Its tokens are billed at the normal price plus 20%.
- `max_model_usd` number
  With `model`: the most its tokens may cost, surcharge included. Defaults to an estimate from `max_steps` and the model's prices.
- `stream` boolean, default `false`
  `true` returns the run's [event stream](#stream-a-run) (`text/event-stream`) instead of the `202` JSON.
- `screen` boolean, default `false`
  With `stream`: add `frame` events with the browser's screen.
:::

```bash title="cURL" aside
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" aside
import os
import requests

run = requests.post(
    "https://lovelyrouter.com/v1/browser/runs",
    headers={"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"},
    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()
```

```js title="Node.js" aside
const run = await fetch('https://lovelyrouter.com/v1/browser/runs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LOVELY_API_KEY}`,
    'content-type': 'application/json',
  },
  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());
```

:::aside
```json title="Response"
{
  "id": "brun_3f9a1c7e0b2d4e6f8a9b0c1d",
  "object": "browser_run",
  "status": "running",
  "goal": "Find the price of the book \"A Light in the Attic\" and whether it is in stock.",
  "start_url": "https://books.toscrape.com/",
  "engine": "chromium",
  "mode": "fast",
  "model": null,
  "steps": 0,
  "max_steps": 50,
  "billable_time_s": 0,
  "time_usd": 0,
  "current_url": "https://books.toscrape.com/",
  "answer": null,
  "data": null,
  "error": null,
  "needs": null,
  "profile_saved": null,
  "usage": { "decision_calls": 0, "llm_calls": 0 },
  "created_at": "2026-10-11T09:30:00.000Z",
  "finished_at": null,
  "poll": "/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d?wait=30",
  "events": "/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d/events",
  "charge_usd": null,
  "reserved_usd": 0.445
}
```
:::

## Retrieve a run

::endpoint GET /v1/browser/runs/{id}

Returns the run object. With `wait`, the request holds until the run's status (or the kind of pause) changes, or until `wait` seconds pass, then returns the current state. While a run is `waiting_for_human`, it returns at once.

:::params Query parameters
- `wait` integer, default `0`
  Seconds to hold the request while the run is `running`, up to 50.
- `trace` string
  `1` adds `trace`, the list of steps so far.
:::

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

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

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

```js title="Node.js" aside
const API = 'https://lovelyrouter.com/v1/browser';
const headers = { Authorization: `Bearer ${process.env.LOVELY_API_KEY}` };

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

:::aside
The response is the [run object](#the-run-object), with `trace` when you pass `trace=1`.
:::

## Stream a run

::endpoint GET /v1/browser/runs/{id}/events

Returns the run's live events as Server-Sent Events (`text/event-stream`) until the run ends. The first event is the run object with `trace`; then `activity` (what the agent is doing now), `step` (each finished step), `run` (after every step and when a pause begins or ends) and, with `screen`, `frame` (the browser's screen). The last events are the finished run, with `charge_usd`, and `end`. A finished run sends its `run` event and `end` at once. Lines starting with `:` are keep-alive comments. See [Live events & screen](/docs/browser-agent/live).

:::params Query parameters
- `screen` string
  `1` adds `frame` events: `data` (a base64 JPEG of the browser) and `at`. One on connect, then whenever the page changes, at most about 4 a second.
:::

| Event | `data` |
| --- | --- |
| `run` | The [run object](#the-run-object) with `charge_usd` and `reserved_usd`; the first has `trace` |
| `activity` | `step`, `kind` (`starting`, `reading`, `waiting`, `deciding`, `thinking`, `checking`, `acting`, `answering`, `extracting`) and `text` |
| `step` | One `trace` entry |
| `frame` | `data` (base64 JPEG) and `at` |
| `end` | `id` |

```bash title="cURL" aside
curl -N "https://lovelyrouter.com/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d/events?screen=1" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```sse title="Response"
event: run
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d","object":"browser_run","status":"running","steps":1,"trace":[…],…}

event: frame
data: {"data":"/9j/4AAQSkZJRgABAQ…","at":"2026-10-11T09:30:03.120Z"}

event: activity
data: {"step":2,"kind":"answering","text":"Reading the result and writing the answer"}

event: step
data: {"step":2,"url":"https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html","action":"done","description":"Read the result and wrote the answer","ms":2875,"error":null}

event: run
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d","status":"succeeded","answer":"\"A Light in the Attic\" costs £51.77 and is in stock (22 available).","charge_usd":0.012,…}

event: end
data: {"id":"brun_3f9a1c7e0b2d4e6f8a9b0c1d"}
```
:::

## List runs

::endpoint GET /v1/browser/runs

Returns your 50 most recent browser agent runs, newest first, in a short form. Retrieve a run for its full state.

| Field | Meaning |
| --- | --- |
| `id` | The run id |
| `billing_status` | `running` until the run is settled, then `succeeded`, `failed`, `timed_out` or `stopped` |
| `goal` | The first 200 characters of the goal |
| `steps` | Billed steps, `0` until the run is settled |
| `created_at` | When the run started |

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

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "brun_3f9a1c7e0b2d4e6f8a9b0c1d",
      "billing_status": "succeeded",
      "goal": "Find the price of the book \"A Light in the Attic\" and whether it is in stock.",
      "steps": 2,
      "created_at": "2026-10-11T09:30:00.000Z"
    }
  ]
}
```
:::

## Reply to a run

::endpoint POST /v1/browser/runs/{id}/reply

Answers a run that is `waiting_for_human`. Send exactly one kind of reply, matching `needs.kind`. Returns `200` with the run object, its `status` back to `running`. See [Human in the loop](/docs/browser-agent/human-in-the-loop).

:::params Body parameters
- `answer` string
  For a `question`: text the agent reads. Up to 2,000 characters.
- `secret` string
  For a `question`: a value the agent types on the current site without seeing it. Up to 4,096 characters. It can go into any text-like field there, password and PIN fields included.
- `approve` boolean
  For an `approval`: `true` to let the agent click the button, `false` to refuse.
- `deny` boolean
  For an `approval`: `true` is the same as `"approve": false`.
- `reason` string
  With a refusal: why, for the agent to read. Up to 500 characters.
- `done` boolean
  For a `takeover`: `true` when the person has finished. The agent continues from the current page.
- `failed` boolean
  For a `takeover`: `true` when the person couldn't finish.
- `note` string
  With `done` or `failed`: what the person did, for the agent to read. Up to 500 characters.
:::

Errors: `400` when the body matches no reply shape, `409` `This run is not waiting for a reply`, and `409` `This run is waiting for a reply of kind "question"` (or `approval`, `takeover`) when the reply doesn't match the pause.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/reply \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"secret": "481923"}'
```

:::aside
```json title="Reply bodies"
{"answer": "Use the billing address."}
{"approve": true}
{"approve": false, "reason": "Wrong delivery date"}
{"done": true, "note": "Solved the CAPTCHA"}
{"failed": true, "note": "The site asked for a phone call"}
```
:::

## Take over a run

::endpoint POST /v1/browser/runs/{id}/takeover

Asks the run to hand you the browser after the step in progress. Returns `202` with the run object. The next status shows `waiting_for_human` with `needs.kind` `takeover` and a `live_view_url`. Hand back with a `{"done": true}` [reply](#reply-to-a-run).

If the run is waiting for a question or an approval, the takeover starts after you reply to it. A finished run returns `409` with `This run has finished`.

:::params Body parameters
- `instructions` string
  Shown as the pause's `prompt`. Up to 500 characters. Default: `The user asked to take over the browser.`
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/takeover \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"instructions": "I will pick the delivery slot myself."}'
```

## Stop a run

::endpoint POST /v1/browser/runs/{id}/stop

Stops a run. It ends as `stopped` at the next step boundary, or right away if it is waiting for a person. Returns `202` with the run object as it was; the next status shows `stopped`. A stopped run pays for the steps it took and returns no `data`.

```bash title="cURL" aside
curl -X POST https://lovelyrouter.com/v1/browser/runs/brun_3f9a1c7e0b2d4e6f8a9b0c1d/stop \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

## Get a screenshot

::endpoint GET /v1/browser/runs/{id}/screenshot

Returns a JPEG (`image/jpeg`) of the browser. While the run has its browser open, that is the screen as it is now; otherwise the latest one kept: the page when a pause for a person began, or the final page of a finished run. `needs.screenshot` holds this path. Returns `404` with `No screenshot yet` when there is none. To watch the screen continuously, [stream the run](#stream-a-run) with `screen=1`.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/runs/brun_5b7d9f1a3c5e7a9b1d3f5a7c/screenshot \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -o screen.jpg
```

## Store a secret

::endpoint PUT /v1/browser/vault/{name}

Stores a write-only secret, or replaces the one with this name. The agent can type it only on the listed origins, and the model never sees it. See [Logins, secrets & profiles](/docs/browser-agent/logins).

:::params Path parameters
- `name` string, required
  1 to 64 characters: lowercase `a-z`, `0-9`, `_`, `.`, `-`, starting with a letter or digit.
:::

:::params Body parameters
- `value` string, required
  The secret, up to 4 KB (larger values return `413`). For `totp`: the base32 seed (16+ characters of `A-Z`, `2-7`).
- `kind` string, default `password`
  `password` (password fields only), `username` or `text` (text-like fields, not password fields), or `totp` (typed as the current 6-digit code).
- `origins` array, required
  1 to 20 origins like `https://github.com` or `https://*.example.com`. `https` only, except `http://localhost` and `http://127.0.0.1`.
:::

```bash title="cURL" aside
curl -X PUT https://lovelyrouter.com/v1/browser/vault/github_password \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"value": "correct-horse-battery-staple", "kind": "password", "origins": ["https://github.com"]}'
```

:::aside
```json title="Response"
{ "name": "github_password", "kind": "password", "origins": ["https://github.com"], "stored": true }
```
:::

## List secrets

::endpoint GET /v1/browser/vault

Lists your secrets by name, without values.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/vault \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "name": "github_password",
      "kind": "password",
      "origins": ["https://github.com"],
      "created_at": "2026-10-11T09:00:00.000Z",
      "updated_at": "2026-10-11T09:00:00.000Z",
      "last_used_at": "2026-10-11T09:12:31.000Z"
    }
  ]
}
```
:::

## Delete a secret

::endpoint DELETE /v1/browser/vault/{name}

Deletes a secret. Runs that list it can no longer type it. Returns `404` with `No such secret` if there is none.

```bash title="cURL" aside
curl -X DELETE https://lovelyrouter.com/v1/browser/vault/github_password \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{ "deleted": "github_password" }
```
:::

## Upload a file

::endpoint PUT /v1/browser/files/{name}

Uploads a file the agent may attach to forms, or replaces the file with this name (keeping its `id`). Send the raw bytes as the body and the file type as `Content-Type`. See [File uploads](/docs/browser-agent/files).

:::params Path and query parameters
- `name` string, required
  In the path, URL-encoded. 1 to 128 characters: letters, digits, spaces and `. _ - ( ) + ,`, starting with a letter, digit or `_`.
- `description` string
  Query parameter. What the file is, to help the agent choose. Up to 300 characters.
:::

Files are limited to 10 MB (`413`) and 100 per account (`409` `file limit reached (100); delete some first`).

```bash title="cURL" aside
curl -X PUT "https://lovelyrouter.com/v1/browser/files/Maria_Garcia_CV_2026.pdf?description=Resume" \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/pdf" \
  --data-binary @Maria_Garcia_CV_2026.pdf
```

:::aside
```json title="Response"
{
  "id": "file_7a3c9e1b5d2f8a4c6e0b",
  "name": "Maria_Garcia_CV_2026.pdf",
  "mime": "application/pdf",
  "bytes": 84213,
  "sha256": "5f2b8c…",
  "description": "Resume",
  "created_at": "2026-10-11T09:20:44.000Z",
  "last_used_at": null
}
```
:::

## Upload a file with a form

::endpoint POST /v1/browser/files

The same as [Upload a file](#upload-a-file), as `multipart/form-data`. Returns the same file object.

:::params Form fields
- `file` file, required
  The file. Its type comes from the part's content type.
- `name` string
  The name to store it under. Defaults to the uploaded file's name.
- `description` string
  What the file is. Up to 300 characters.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/files \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -F "file=@Maria_Garcia_CV_2026.pdf;type=application/pdf" \
  -F "description=Resume"
```

## List files

::endpoint GET /v1/browser/files

Lists your uploaded files, sorted by name. `last_used_at` is when a run last attached the file.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/files \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "file_7a3c9e1b5d2f8a4c6e0b",
      "name": "Maria_Garcia_CV_2026.pdf",
      "mime": "application/pdf",
      "bytes": 84213,
      "sha256": "5f2b8c…",
      "description": "Resume",
      "created_at": "2026-10-11T09:20:44.000Z",
      "last_used_at": "2026-10-11T09:47:10.000Z"
    }
  ]
}
```
:::

## Delete a file

::endpoint DELETE /v1/browser/files/{name}

Deletes a file by name or id. Returns `404` with `No such file` if there is none.

```bash title="cURL" aside
curl -X DELETE https://lovelyrouter.com/v1/browser/files/Maria_Garcia_CV_2026.pdf \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{ "deleted": "Maria_Garcia_CV_2026.pdf" }
```
:::

## List profiles

::endpoint GET /v1/browser/profiles

Lists your saved sessions, most recently updated first. A run creates or updates a profile with `save_profile` and starts from one with `profile`. `origins` are the sites the profile holds cookies or local storage for; `bytes` is its size.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/browser/profiles \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [
    {
      "id": "prof_4c1e9a7b2d0f3e5a8b6c",
      "name": "shop-login",
      "origins": ["https://shop.example.com"],
      "bytes": 18342,
      "created_at": "2026-10-10T16:02:11.000Z",
      "updated_at": "2026-10-11T09:15:40.000Z",
      "last_used_at": "2026-10-11T09:14:58.000Z"
    }
  ]
}
```
:::

## Delete a profile

::endpoint DELETE /v1/browser/profiles/{id}

Deletes a profile by id or name. Returns `404` with `No such profile` if there is none.

```bash title="cURL" aside
curl -X DELETE https://lovelyrouter.com/v1/browser/profiles/shop-login \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{ "deleted": "shop-login" }
```
:::

## Errors

Errors return a JSON body with a `type` and a human-readable `message`. Messages name the field at fault. See also [Errors](/docs/api/errors).

| Status | `type` | When |
| --- | --- | --- |
| `400` | `invalid_request_error` | A body or parameter is invalid, e.g. `"goal" is required (max 4,000 characters)`, `Unknown vault secret(s): …`, `Unknown file(s): …`, `Unknown profile "…"`. |
| `401` | `authentication_error` | The API key is missing, invalid, revoked or expired. |
| `402` | `insufficient_quota` | Your balance is below the run's hold, or the hold would pass the key's spend limit. |
| `404` | `not_found` | No such run, secret, file, profile or screenshot in your account. |
| `409` | `invalid_request_error` | The run isn't waiting for this reply, has finished, or you have reached 100 files. |
| `413` | `invalid_request_error` | A file is larger than 10 MB, or a vault secret larger than 4 KB. |
| `429` | `rate_limit_exceeded` | 10 runs (browser agent and data tool runs together) are already running. |
| `502` | `tool_unavailable` | The run could not start. You were not charged; try again. |
| `503` | `tool_unavailable` | The browser agent is temporarily unavailable. |

:::aside
```json title="Error"
{
  "error": {
    "type": "invalid_request_error",
    "message": "\"start_url\" must be an http(s) URL"
  }
}
```
:::
