> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser

> Drive a computer's managed browser over REST — actions, session state, saved logins and who may reopen them.

<Info>The option object, guards and lifetime are in the [Browser](/docs/computer/browser) guide. This page is the routes.</Info>

A [computer](/docs/api/computers) created with a `browser` block carries one **browser session**. The
first action opens it; `DELETE` closes it. Every route below takes the `computers:write` scope
unless it says otherwise. Three routes — live view, granting and revoking a saved login — answer
only a signed-in dashboard session: an API key gets `403 forbidden` whatever its scopes.

## The browser session object

<ResponseField name="id" type="string">Unique id (e.g. `brw_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `browser_session`.</ResponseField>
<ResponseField name="computer_id" type="string | null">The computer the browser runs on.</ResponseField>
<ResponseField name="session_id" type="string | null">The [agent session](/docs/api/sessions) whose `browser` tool opened it; `null` for a browser opened over the API.</ResponseField>
<ResponseField name="status" type="string">`creating`, `active`, `closed` or `error`.</ResponseField>
<ResponseField name="allowed_domains" type="string[]">The allow-list every action is checked against.</ResponseField>
<ResponseField name="region" type="string">`us-west`, `us-east`, `eu-central` or `ap-southeast`.</ResponseField>
<ResponseField name="context_id" type="string | null">The saved login (`bct_`) this session reopened, if any.</ResponseField>
<ResponseField name="context_backed" type="boolean">`true` when the session carries a saved login — `extract`, `observe`, `links` and `screenshot` then need `allow_extract`.</ResponseField>
<ResponseField name="timeout_at" type="string">When the session closes on its own (`timeout_minutes` after it opened). A sweep on the platform clock closes and bills any session past it within a minute; the row is then `closed` like one you deleted.</ResponseField>
<ResponseField name="opened_at" type="string">When the browser came up.</ResponseField>
<ResponseField name="closed_at" type="string | null">When it closed; `null` while open.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

The live-view URL and the upstream connect URL are bearer credentials. Neither is a field on this
object, and neither appears in a tool result, an event or a log.

## Perform an action

`POST /v1/computers/{id}/browser/actions` — opens the browser lazily on the first call.

The body is **one action**, discriminated on `action`. `Idempotency-Key` is **required** for `goto`
and `act` and optional elsewhere.

| `action`     | body                                   | how it runs                                                                                        |
| ------------ | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `goto`       | `{ url }`                              | deterministic                                                                                      |
| `click`      | `{ selector }`                         | deterministic                                                                                      |
| `type`       | `{ selector, text }`                   | deterministic; credential-shaped `text` is refused                                                 |
| `extract`    | `{ instruction?, selector?, schema? }` | selector only → the element's text; with `instruction`/`schema` → model-driven, shaped to `schema` |
| `act`        | `{ instruction, variables? }`          | model-driven; `%name%` placeholders are filled from `variables`                                    |
| `observe`    | `{ instruction }`                      | model-driven → candidate elements                                                                  |
| `links`      | `{ contains?, limit? }`                | deterministic; `limit` defaults to 50, max 200                                                     |
| `screenshot` | `{}`                                   | deterministic → a `fil_` file                                                                      |

<CodeGroup>
  ```bash cURL theme={"system"}
  curl https://api.vetta.sh/v1/computers/cmp_01H.../browser/actions \
    -H "Authorization: Bearer $VETTA_API_KEY" \
    -H "Idempotency-Key: 5f3b0e2e-1" \
    -H "Content-Type: application/json" \
    -d '{ "action": "act", "instruction": "search for %term%", "variables": { "term": "vetta" } }'
  ```
</CodeGroup>

Every action answers the same object:

<ResponseField name="object" type="string">Always `browser_action`.</ResponseField>
<ResponseField name="action" type="string">The action that ran.</ResponseField>
<ResponseField name="url" type="string">The page URL after the action.</ResponseField>
<ResponseField name="text" type="string | null">`extract` without a schema: the text read.</ResponseField>
<ResponseField name="data" type="unknown | null">`extract` with a schema: the data, in that shape.</ResponseField>
<ResponseField name="links" type="object[] | null">`links`: `[{ href, text }]`.</ResponseField>
<ResponseField name="elements" type="object[] | null">`observe`: `[{ selector, description }]`.</ResponseField>
<ResponseField name="file_id" type="string | null">`screenshot`: the `fil_` id of the PNG. Never the bytes.</ResponseField>
<ResponseField name="performed" type="boolean">Always `true` on a `200`. An `act` that did nothing is a `502 provider_error`, not a `200` with `false`.</ResponseField>

`variables` values are never echoed — not in the result, not in the event, not in the audit row.

### What is refused

| Guard                                                                                                                                                                                                        | Error                                                  |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| Page URL off the allow-list, or a private, loopback or metadata host — checked before **and** after every action                                                                                             | `403 forbidden`, `param: url`                          |
| Write-looking `act`, `click` or `type` (submit, purchase, delete, send, pay, confirm) without `allow_writes`                                                                                                 | `403 forbidden`, `param: allow_writes`                 |
| Credential-shaped `type.text` or `act.instruction` (password, token and key patterns; vault values are sealed and never compared) — credentials travel only through [login](#log-in-with-a-saved-credential) | `400 validation_failed`, `param: text` / `instruction` |
| `extract`, `observe`, `links` or `screenshot` on a context-backed session without `allow_extract`                                                                                                            | `403 forbidden`, `param: allow_extract`                |
| An `act` the automation could not perform                                                                                                                                                                    | `502 provider_error`                                   |
| A `context_name` the calling agent has no grant for                                                                                                                                                          | `403 forbidden`, `param: context_name`                 |
| A `context_name` that names a saved login on more than one identity — the lookup is organization-wide by name, and a computer's options carry no identity to pick one                                        | `400 validation_failed`, `param: context_name`         |

<Note>
  Name saved logins uniquely across the organization if more than one identity will hold them: `shop-ava` rather than two `shop`s. A name is unique per identity at save time, so the collision only arises across identities.
</Note>

## Read the session

`GET /v1/computers/{id}/browser` → the [browser session](#the-browser-session-object), or `404 not_found` when none is open.

## Close the session

`DELETE /v1/computers/{id}/browser` — idempotent. The closed [browser session](#the-browser-session-object)
(`status: closed`, `closed_at` set), or `204` when nothing was open. Closing settles the session's
minutes on the `browser` line item, capped at `timeout_at`, and, on a session opened with `persist_context` or `human_login`,
writes its cookies and storage into the saved login.

## Live view

`GET /v1/computers/{id}/browser/live-view` — **dashboard session only.** A `browser_live_view`
(`{ object, url, expires_at }`) for the one browser currently open; the URL is a bearer credential
and is never logged or put in an event. An API key gets `403 forbidden`.

The dashboard's Browser tab on a computer renders it, and lists every browser the computer ever
drove — see [Browser sessions](/docs/api/browser-sessions). Never hand the URL to an agent or store it.

## Save a login

`POST /v1/computers/{id}/browser/context` → `201 browser_context`

A saved login is **bound when a browser opens**: the engine attaches it at session start and flushes
cookies and storage into it at close. This call therefore has two moments:

1. **No browser open on the computer** — creates the empty saved login under `name`, owned by an
   [identity](/docs/api/identities). Open the next browser with `context_name` (and `human_login` or
   `persist_context`) to fill it.
2. **A browser open with that `context_name`** — marks the login saved and releases the
   `human_login` lock; the same `bct_` comes back and the close persists the login.

On a browser open without that context the call is `400 validation_failed` (`param: context_name`):
nothing that browser holds can reach the saved login.

<ParamField body="name" type="string" required>1–64 characters; unique per identity.</ParamField>
<ParamField body="identity_id" type="string" required>The `idn_` that owns the saved login.</ParamField>
<ParamField body="service" type="string">What the login is for, e.g. `figma.com`.</ParamField>

<ResponseField name="id" type="string">`bct_` id.</ResponseField>
<ResponseField name="object" type="string">Always `browser_context`.</ResponseField>
<ResponseField name="identity_id" type="string">The owning identity.</ResponseField>
<ResponseField name="name" type="string">The name a later `context_name` reopens.</ResponseField>
<ResponseField name="service" type="string | null">As given.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>
<ResponseField name="last_used_at" type="string | null">When a session last reopened it.</ResponseField>

## Grant a saved login

`POST /v1/browser/contexts/{name}/grants` — **dashboard session only** → `201 browser_grant`

A saved login is default-deny: a session may reopen it only when its agent — or the agent's member
role — holds a grant. The same grantee twice is a `200` replay of the existing grant; a new one
reaches your [webhooks](/docs/api/webhooks) as `browser.context.granted`. Over the REST
API, an **owner or admin** dashboard session — or an API key holding the `admin` scope — opens every
saved login of the organization without a grant; a **member** session, and any other API key, needs
the `member` role grant; an agent's `browser` tool needs a grant by its `agt_` id or by role.

<ParamField body="identity_id" type="string" required>The identity that owns `{name}`.</ParamField>
<ParamField body="grantee_type" type="string" required>`agent` or `role`.</ParamField>
<ParamField body="grantee_id" type="string" required>An `agt_` id, or `owner` / `admin` / `member`.</ParamField>

<ResponseField name="id" type="string">`bgr_` id.</ResponseField>
<ResponseField name="object" type="string">Always `browser_grant`.</ResponseField>
<ResponseField name="context_id" type="string">The `bct_` granted.</ResponseField>
<ResponseField name="grantee_type" type="string">`agent` or `role`.</ResponseField>
<ResponseField name="grantee_id" type="string">As given.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

## Revoke a saved login

`DELETE /v1/browser/contexts/{name}?identity_id=idn_...` — **dashboard session only.** Deletes the
saved login and every grant on it. `identity_id` is required. Webhooks receive
`browser.context.revoked { context_id }`.

## Log in with a saved credential

`POST /v1/computers/{id}/browser/login` → `browser_action`

Reads the identity's vault credential `login:<service>` and fills the form through `%username%` and
`%password%` placeholders. The plaintext never enters the model or a result.

<ParamField body="service" type="string" required>The credential's service, e.g. `figma.com`.</ParamField>
<ParamField body="url" type="string" required>The sign-in page.</ParamField>
<ParamField body="identity_id" type="string" required>Whose vault to read.</ParamField>

## Sign up

`POST /v1/computers/{id}/browser/signup` → `browser_action`

Generates a password, stores it as `login:<service>` in the identity's vault, then runs the sign-up
flow with it. When an agent's `browser` tool does this, the session **always pauses for a person to
confirm** — see [Tools](/docs/capabilities/tools).

<ParamField body="service" type="string" required>The service being joined.</ParamField>
<ParamField body="url" type="string" required>The sign-up page.</ParamField>
<ParamField body="identity_id" type="string" required>Whose vault receives the credential.</ParamField>
<ParamField body="email" type="string" required>The address to register.</ParamField>

## Save a credential

`POST /v1/browser/credentials` → the [vault credential](/docs/api/vaults)'s metadata

Seals `login:<service>` into the identity's vault so `login` can use it. The reply is metadata —
no route ever returns the value.

<ParamField body="identity_id" type="string" required>Whose vault.</ParamField>
<ParamField body="service" type="string" required>The service, e.g. `figma.com`.</ParamField>
<ParamField body="email" type="string" required>The account's email.</ParamField>
<ParamField body="password" type="string" required>Write-only.</ParamField>
<ParamField body="username" type="string">When the site signs in by username rather than email.</ParamField>

## Errors

`forbidden`, `validation_failed`, `not_found`, `context_locked` (`409` — a `human_login` flow holds
the saved login for 15 minutes), `provider_error` (`502`), `computer_unavailable`, `rate_limited`,
`feature_not_configured`. See [Errors](/docs/api/errors).
