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

# Browser

> Cloud browser sessions your agents drive — navigate, act, extract — plus autonomous signup/login that generates and vaults credentials, with a human live view.

The browser primitive gives your agents a real, cloud-hosted browser they can drive step by step (navigate, act, extract, observe, screenshot), reusable saved logins, and two high-level autonomous actions — **signup** and **login** — that handle account creation and re-authentication end to end. Sessions are per-user, scoped to an allowlist of domains, metered per action, and watchable live from the dashboard.

It is a per-tenant-user primitive: every session belongs to a `tenant_user` and is gated by that user's [Account Kit](/docs/architecture/account-kits) (`browser` must be enabled). Enable or disable it per kit, and optionally require [human approval](/docs/architecture/approvals) for autonomous signup.

## CLI First

```bash theme={"theme":"css-variables"}
# Drive a session step by step
naive browser session create --allowed-domains example.com --timeout 15
naive browser navigate <session_id> https://example.com
naive browser act <session_id> "click the search button"
naive browser extract <session_id> "the list of product names and prices"
naive browser links <session_id> --contains linkedin.com   # real hrefs + current URL
naive browser session close <session_id>

# Autonomous signup / login (credentials are generated + stored for you)
naive browser signup figma.com https://figma.com/signup
naive browser login  figma.com https://figma.com/login
```

## How it works

| Capability       | What it does                                                                         | Cost                            |
| ---------------- | ------------------------------------------------------------------------------------ | ------------------------------- |
| `session create` | Open a live cloud browser scoped to `allowed_domains`                                | 0 credits (time floor at close) |
| `navigate`       | Go to a URL (allowlist + SSRF enforced)                                              | 10 credits                      |
| `act`            | Natural-language action (click/fill/scroll); returns the page `url` after the action | 10 credits                      |
| `extract`        | Pull structured data (visible text) from the page (read-only)                        | 10 credits                      |
| `links`          | Read anchor `href`s + the current URL via a DOM read (read-only)                     | 10 credits                      |
| `observe`        | List candidate elements/actions (read-only)                                          | 10 credits                      |
| `screenshot`     | Capture the page (short-lived signed URL)                                            | 10 credits                      |
| `signup`         | Autonomous account creation + credential vaulting                                    | 40 credits                      |
| `login`          | Autonomous re-authentication from the vault                                          | 30 credits                      |

Sessions have a hard TTL (default 15 min, max 30) and a per-session credit ceiling of 400. Always close sessions when done — an idle session still bills a vendor **time floor** at close, based on actual elapsed time: 10 credits (≤5 min), 20 (≤15 min), 30 (≤30 min).

## Reading URLs & links

`extract` reasons over the page's **accessibility tree** — the same semantic view a screen
reader sees. That tree carries roles and *visible text*, but **not** raw DOM attributes like a
link's `href` or `window.location`. So asking `extract` for "the LinkedIn URL" or "the current
page URL" returns visible text (or a fabricated-looking guess), never the real target. Two
purpose-built reads cover URLs:

<CardGroup cols={2}>
  <Card title="links — link targets" icon="link">
    `links(session_id, { contains?, limit? })` does a direct DOM read and returns
    `{ url, links: [{ text, href }] }`. This is the reliable way to capture real hrefs
    (LinkedIn / GitHub / profile links). Filter with `contains` (matches href **or** text) and cap
    with `limit` (default 300, max 1000).
  </Card>

  <Card title="act / navigate — the current URL" icon="location-dot">
    `act` and `navigate` both return the page **`url`** *after* the step runs — the way to learn
    where a click landed, including SPA route changes that put an opaque token in the path (e.g.
    `/applicants/<token>`), which no page-content read can reconstruct.
  </Card>
</CardGroup>

```bash theme={"theme":"css-variables"}
naive browser act <session_id> "open the first candidate" # → result.url = https://…/applicants/abc123
naive browser links <session_id> --contains linkedin.com  # → [{ text, href }], plus the current url
```

`links` is read-only and, on a logged-in (context-backed) saved-login session, is
capability-gated (`--allow-extract`) and output-redacted exactly like `extract`.

## Autonomous signup & login

This is the core workflow: an agent that needs an account on a third-party service can create one and reuse it later, without ever handling the password itself.

<Steps>
  <Step title="Signup">
    `signup` opens a scoped, write-enabled session, fills the registration form with the user's identity (email + name) and a **strong generated password**, submits, and stores `{ email, password }` in the user's encrypted [Vault](/docs/getting-started/vault) under the key `login:<service>`. The password is never returned to the agent or sent to the model.
  </Step>

  <Step title="Approval (optional)">
    Because signup creates a real account under the user's identity, it is approval-gated by default. When gated, the call returns `status: "pending_approval"` (HTTP 202) and runs only after a human approves it. Toggle this per Account Kit.
  </Step>

  <Step title="Login later">
    When a session expires and the agent needs the service again, `login` reads the stored credential from the Vault and signs back in — again without the password crossing the agent/LLM boundary.
  </Step>
</Steps>

<Info>
  Secrets never reach the model. The password is filled via Stagehand variable substitution (`%password%` is a placeholder; the real value is substituted locally and is never logged or sent to the LLM). Signup uses the tenant user's profile `email` as the account email — point it at a provisioned [inbox](/docs/getting-started/email) so verification emails can be received. Set it with the [`profile`](/docs/sdk/sub-clients/profile) primitive (`client.forUser(id).profile.setEmail(...)`, or `profile.set_email` from the agent toolset).

  If you already have a working email/password for the service, store it with `saveCredential` (`POST .../browser/credentials`) instead of typing it via `act`; a later `login` then pulls it from the Vault server-side.
</Info>

## Saved logins (Tier B)

For services where a human must log in once (SSO, 2FA, CAPTCHAs), open a `human_login` session, complete the login in the **dashboard live view**, then `save_login` to persist it as a reusable, encrypted vendor-side context. naive stores only an opaque pointer — never the cookies or credentials. Later sessions reopen already-logged-in by name (`--context-name`), gated by a human-created grant (default-deny; an agent can use a saved login but never create the grant or revoke it).

## Live view

From the dashboard, open a user's **Browser** tab to see their sessions. For an active session you can watch it stream in real time and click **Take control** to interact directly — this is how a human completes the one-time login for a saved-login context, or steps in to solve a CAPTCHA.

## Safety

* **Domain allowlist (default-deny).** Every session must pass `allowed_domains`; pass `['*']` to browse unrestricted (not recommended — no DNS-rebinding protection). An SSRF denylist (private/loopback/metadata hosts, non-http(s) schemes) always applies.
* **Writes are gated.** Destructive/submit actions are rejected unless the session was opened with `allow_writes`.
* **No secrets in instructions.** Never put passwords in an `act`/`extract` instruction — the autonomous signup/login flow handles credentials server-side.
* **Read-restricted saved logins.** On a logged-in (context-backed) session, `extract`/`observe`/`screenshot` are disabled unless a human opened it with `allow_extract`, and their output is redacted.
