# Installation Source: https://browserhive.ai/docs/guide/installation/ BrowserHive ships as one npm package, `browserhive`, which provides the `browserhive` command and a programmatic API. It runs on **Bun**. You can install it with bun, npm or pnpm, but Bun must be installed to run it. ## Requirements | Requirement | Why | How | |---|---|---| | **Bun ≥ 1.4** | The only supported runtime. BrowserHive uses Bun's HTTP server, SQLite and password hashing directly. | macOS/Linux: `curl -fsSL https://bun.sh/install \| bash`. Windows: `powershell -c "irm bun.sh/install.ps1 \| iex"` | | **Chromium** | The browser that runs every session. | Installed once by `browserhive init`. It is never downloaded during `npm install`. | | **Bitwarden CLI (`bw`)** | Only if you use the vault (`--vault bitwarden`). | See [the vault guide](/docs/guide/vault/). | | macOS, Linux or Windows | | | Node.js is not a supported runtime. `npm` and `pnpm` work for installing because the `browserhive` command starts with `#!/usr/bin/env bun`. ## Install the package Pick one: ```bash bun add -g browserhive npm i -g browserhive pnpm add -g browserhive ``` Without a global install, `bunx browserhive` (or `npx browserhive`, with Bun on `PATH`) works for every command below. ## Install the browser and prepare the data directory ```bash browserhive init ``` `init` is the one-time setup. Each step is idempotent and reported with ✓ or ✗: 1. Creates the data directory (mode `0700`) and its subdirectories. 2. Downloads Chromium for the pinned Playwright version. `PLAYWRIGHT_BROWSERS_PATH` is honoured. 3. Downloads the Patchright build of Chromium, used by stealth sessions, unless `--stealthDriver playwright` is set. 4. Creates the database and applies migrations. 5. Prints next steps. Useful flags: `--force` re-downloads the browsers, `--dataDir ` and `--config ` choose where state lives. Network access is only needed for the browser download. Re-run `init` after upgrading BrowserHive; it only downloads what changed. If you skip `init`, the first `launch_session` fails with [`BROWSER_NOT_INSTALLED`](/docs/reference/errors/#BROWSER_NOT_INSTALLED) and the message names the command to run. ## Check the host ```bash browserhive doctor ``` `doctor` prints a table with one row per check and the fix for each failure: - Bun version is at least 1.4. - Chromium is installed for the resolved stealth driver (Playwright and Patchright), with exact versions. - The data directory exists, has owner-only permissions and has free disk space. - The configuration is valid. It runs the full resolver and prints any shadow lines. - The port is free on the configured host. - `bw` is on `PATH` when `vault=bitwarden`. - The database opens, with its schema version, pending migrations and last backup. - The OTLP endpoint is reachable when `otel=true` (a warning only). - `maxSessions` makes sense for the host's RAM. - A config file that contains `authTokens` is not readable by other users. Exit code `0` means every check passed, `2` means warnings only, `1` means at least one check failed. `browserhive doctor --json` prints the same data as an array of `{ check, status, detail }`. ## Where BrowserHive keeps its data | OS | Default data directory | |---|---| | macOS | `~/Library/Application Support/BrowserHive` | | Windows | `%LOCALAPPDATA%\BrowserHive` | | Linux | `$XDG_DATA_HOME/browserhive`, or `~/.local/share/browserhive` | Override it with `--dataDir` or `BROWSERHIVE_DATA_DIR`. The layout: ``` / browserhive.db SQLite: sessions, audit trail, auth, vault policy, notifications backups/ database backups written before migrations admin/credentials.txt first-run dashboard password (removed after you change it) sessions// userdata, trace.zip, screenshots/, downloads/ auth-states/ saved logins (storage states, profile zips) uploads/ browserhive.config.json optional configuration file ``` Directories are created with mode `0700` and secret files with `0600`. ## Next - [Quick start](/docs/guide/quick-start/) - [Connect your MCP client](/docs/guide/mcp-clients/) --- # Quick start Source: https://browserhive.ai/docs/guide/quick-start/ This page assumes you have [installed BrowserHive](/docs/guide/installation/) and run `browserhive init`. ## 1. Start the server ```bash browserhive ``` That is the whole setup for a local agent: - MCP is served at `http://127.0.0.1:9876/mcp` (Streamable HTTP). - Sessions are in memory and headless by default. - No dashboard, no vault, no authentication. The server binds to loopback only. Connect your agent with the snippets in [MCP clients](/docs/guide/mcp-clients/). ## 2. Add the dashboard ```bash browserhive --admin ``` The dashboard, REST API and WebSocket share the same port. On the first start the server prints a one-time password: ``` BrowserHive 0.1.0 · bun 1.4.2 · patchright 1.63.0 MCP http://127.0.0.1:9876/mcp (auth: off) Dashboard http://127.0.0.1:9876/ (admin) ... admin: first-run password: Kq7… (also in ~/.local/share/browserhive/admin/credentials.txt) Press Ctrl-C to stop. ``` Open `http://127.0.0.1:9876/`, sign in with that password, and choose a new one (at least 12 characters). The seed file is then overwritten and deleted. With `--admin`, every session also records a Playwright trace that you can replay later. ## 3. Drive a browser from your agent A minimal session, as tool calls: ```jsonc launch_session({ "slug": "shop", "persistence_mode": "persistent" }) // → { "session_id": "shop-a1b2c3d4", ... } navigate({ "session_id": "shop-a1b2c3d4", "url": "https://example.com" }) snapshot({ "session_id": "shop-a1b2c3d4" }) screenshot({ "session_id": "shop-a1b2c3d4" }) close_session({ "session_id": "shop-a1b2c3d4" }) ``` Every session is its own Chromium process with its own cookies, storage, IndexedDB and service workers. The full catalog is in the [tool reference](/docs/reference/tools/). ## Common setups | Goal | Command | What you get | |---|---|---| | Just run it | `browserhive` | MCP on `127.0.0.1:9876/mcp`, memory sessions, headless | | One agent over stdio | `browserhive --transport stdio` | stdio MCP. No dashboard, no attention tools | | Sessions that survive restarts | `browserhive --persistence persistent` | profiles under `/sessions//userdata` | | Watch the agents | `browserhive --admin` | dashboard, live view, traces | | Log in without the model seeing the password | `bw login`, then `browserhive --admin --vault bitwarden`; paste a token from `bw unlock --raw` on the Vault page | see [Vault](/docs/guide/vault/) | | Replay a run | dashboard → Sessions → a session → Files → Open trace viewer | Playwright Trace Viewer with DOM snapshots, network and console | | Keep agents off some sites | `browserhive --admin --blocklist ./blocklist.txt --blocklistWatch` | refused navigations, visible on the Blocklist page | | Human in the loop | `browserhive --admin` and the agent calls `request_attention` | see [Human takeover](/docs/guide/attention/) | | Expose on the LAN | `browserhive --host 0.0.0.0 --auth token --admin` | bearer tokens required; see [Security](/docs/guide/security/) | | Export telemetry | `browserhive --admin --otel --otelEndpoint http://127.0.0.1:4318` | see [Telemetry](/docs/guide/telemetry/) | | Start over | `browserhive purge` | deletes the database and sessions after you type `YES` | Every flag also works as an environment variable and a config-file key. See [Configuration](/docs/guide/configuration/). ## Blocklist file format One glob per line. Blank lines and `#` comments are ignored; matching is case-insensitive. ```text # block a site and all its paths example.com # subdomains only *.tracking.example # one scheme and path https://intranet.example.org/admin/* # substring *doubleclick* ``` Navigations to a blocked URL fail with [`URL_BLOCKED`](/docs/reference/errors/#URL_BLOCKED) before the browser is touched, and document requests are aborted at the network layer. Subresources are not blocked: the blocklist keeps agents off pages, it is not an egress firewall. With `--blocklistWatch` (or the Reload button on the Blocklist page) edits apply without a restart. --- # Connecting MCP clients Source: https://browserhive.ai/docs/guide/mcp-clients/ BrowserHive speaks MCP over two transports: | Transport | Endpoint | Use it when | |---|---|---| | **Streamable HTTP** (default) | `http://127.0.0.1:9876/mcp` | You run one BrowserHive daemon and connect any number of agents to it. Required for the dashboard, human takeover and vault confirmations. | | **stdio** | the client spawns `browserhive --transport stdio` | One client, no daemon to manage. No dashboard, no attention tools, no HTTP listener. | Streamable HTTP is recommended. Start the server first (`browserhive`, or `browserhive --admin`), then point your client at it. ## Authentication tokens With the default `--auth off`, no token is needed and every caller is the principal `local`. That is only allowed on a loopback bind. With `--auth token`, every request to `/mcp` needs `Authorization: Bearer `, and each agent only sees the browser sessions it created. - **First start:** the server creates a token for the principal `agent-1` and prints it once in the startup banner. - **More tokens:** create one per agent. The plaintext is shown only once: ```bash browserhive admin tokens create ci-runner browserhive admin tokens list browserhive admin tokens revoke ci-runner ``` While a server is running, the same commands work against it over the REST API, or you can manage tokens on the dashboard's System page. - **Ephemeral tokens:** `BROWSERHIVE_AUTH_TOKENS=ci-runner:<32+ characters>` adds tokens that are never stored. In the snippets below, replace `` with a real token, or remove the `Authorization` header when auth is off. ## Claude Code HTTP: ```bash claude mcp add --transport http browserhive http://127.0.0.1:9876/mcp --header "Authorization: Bearer " ``` stdio: ```bash claude mcp add browserhive -- browserhive --transport stdio ``` Or commit a project-scoped `.mcp.json`: ```json { "mcpServers": { "browserhive": { "type": "http", "url": "http://127.0.0.1:9876/mcp", "headers": { "Authorization": "Bearer ${BROWSERHIVE_TOKEN}" } } } } ``` ## Claude Desktop Claude Desktop starts local servers from `claude_desktop_config.json` (Settings → Developer → Edit Config). Use stdio: ```json { "mcpServers": { "browserhive": { "command": "browserhive", "args": ["--transport", "stdio"] } } } ``` Claude Desktop does not inherit your shell's `PATH` on every platform. If the server fails to start, use absolute paths: `"command": "/Users/you/.bun/bin/bun"` with `"args": ["/Users/you/.bun/bin/browserhive", "--transport", "stdio"]`. To share one HTTP daemon with Claude Desktop, bridge it with `mcp-remote`: ```json { "mcpServers": { "browserhive": { "command": "npx", "args": ["mcp-remote", "http://127.0.0.1:9876/mcp", "--header", "Authorization:${AUTH_HEADER}"], "env": { "AUTH_HEADER": "Bearer " } } } } ``` ## Cursor `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project): ```json { "mcpServers": { "browserhive": { "url": "http://127.0.0.1:9876/mcp", "headers": { "Authorization": "Bearer " } } } } ``` stdio: ```json { "mcpServers": { "browserhive": { "command": "browserhive", "args": ["--transport", "stdio"] } } } ``` ## VS Code `.vscode/mcp.json`. VS Code prompts for the token once and stores it securely: ```json { "inputs": [ { "id": "browserhive-token", "type": "promptString", "description": "BrowserHive bearer token", "password": true } ], "servers": { "browserhive": { "type": "http", "url": "http://127.0.0.1:9876/mcp", "headers": { "Authorization": "Bearer ${input:browserhive-token}" } } } } ``` stdio: ```json { "servers": { "browserhive": { "type": "stdio", "command": "browserhive", "args": ["--transport", "stdio"] } } } ``` ## Any other client **Streamable HTTP:** `POST`, `GET` and `DELETE` on `http://:/mcp`, with `Authorization: Bearer ` when `--auth token` is on. The server issues an `Mcp-Session-Id` on `initialize`. Responses are streamed as server-sent events so `request_attention` can send progress heartbeats every 25 seconds, and a client that reconnects with `Last-Event-ID` resumes the stream. ```json { "mcpServers": { "browserhive": { "type": "http", "url": "http://127.0.0.1:9876/mcp", "headers": { "Authorization": "Bearer " } } } } ``` **stdio:** spawn `browserhive --transport stdio`. stdout carries only JSON-RPC frames; banners and logs go to stderr. ```json { "mcpServers": { "browserhive": { "command": "browserhive", "args": ["--transport", "stdio"] } } } ``` Requests whose `Host` header is not the bind address, `localhost`, `127.0.0.1` or `[::1]` are rejected (DNS-rebinding protection). ## Optional client metadata The dashboard shows which client and model drive each session. Clients may send these headers (self-reported, display only, never used for access control): | Header | Example | |---|---| | `X-BH-Agent-Model` | `claude-sonnet-4-5` | | `X-BH-Agent-Harness` | `claude-code` | | `X-BH-Workspace` | `checkout-bot` | A W3C `traceparent` in a tool call's `_meta` becomes the parent of the tool span when [telemetry](/docs/guide/telemetry/) is on. ## What differs under stdio - `request_attention` and `get_attention_result` stay listed but return [`ATTENTION_REQUIRES_HTTP`](/docs/reference/errors/#ATTENTION_REQUIRES_HTTP). - The vault works, except entries that require a dashboard confirmation, which are denied automatically. - `--admin` and `--auth token` are rejected at startup. - Every caller is the principal `local`. ## Tool errors A failed tool call returns `isError: true` with the text `[CODE] message`, for example `[URL_BLOCKED] …`. The structured form (`code`, `retryable`, `hint`, `details`) is in the result's `_meta["browserhive.ai/error"]`. Every code is listed in the [error reference](/docs/reference/errors/). --- # Dashboard Source: https://browserhive.ai/docs/guide/dashboard/ The dashboard is the operator's view of every agent: what they are doing, what they visited, what they filled from the vault, and what they need from you. Turn it on with `--admin`. It is served on the same host and port as MCP, at `http://127.0.0.1:9876/`. `--admin` requires `--transport http`. ## Signing in On the first start with `--admin`, BrowserHive generates a 24-character password, prints it once in the startup banner, and writes it to `/admin/credentials.txt` (mode `0600`). Sign in with it; you must choose a new password (12 to 256 characters) before anything else is reachable. The seed file is then overwritten and deleted. Lost the password? Stop the server and run `browserhive admin reset-password`. A dashboard session expires after 15 minutes of inactivity or 8 hours in total. Watching a live view counts as activity. When it expires the dashboard sends you back to the login page. ## Layout and keyboard A sidebar lists the pages (collapsed to icons on medium screens, a drawer on phones). The header shows connection health and notifications. | Key | Action | |---|---| | ⌘K / Ctrl+K | Command palette: pages, sessions, actions | | ⌘B / Ctrl+B | Toggle the sidebar | | `/` | Focus the page's filter | | `?` | Keyboard shortcuts | | Esc | Close the topmost overlay | | `j` / `k`, Enter | Move through and open table rows | Theme follows the system, or pick light or dark. Filters, sorting and pagination live in the URL, so any view can be bookmarked or shared. ## Overview Fleet health over 24 h, 3 d, 7 d, 14 d or 30 d: live sessions, sessions and tool calls in the window (with sparklines), open attention requests, errors with the error rate, active live views, and blocked URLs. The activity chart shows calls per bucket with errors stacked; click a bar to open the sessions of that time slice. Below it, a live feed of websites visited and the most frequent recent failures. ## Sessions Every session, live and finished. Filter by state (live, closed, archived), owner, channel and persistence mode; search by slug or id; sort by any column. Columns show the last URL, calls and errors, blocked attempts, the lease countdown and the state (including "needs attention" and "being watched"). Select rows to archive, unarchive or delete in bulk. Deleting names exactly what is erased: events, trace, screenshots, profile and downloads, terminating live sessions first. ## Session detail The header shows the session id, state and actions: terminate, open the trace viewer, download `trace.zip`, archive, delete. When the agent is waiting on you, a banner shows the attention request or pending vault confirmations. A side rail lists owner, channel, headless or headed, persistence, current URL, lease and identity. Tabs: - **Live.** The screencast of the agent's browser. Controls: start/stop, stream size (fit, 720p, 1080p, native; per viewer), **Resize agent browser** (changes the agent's real viewport), fullscreen. While an attention request is open for this session you can take over: mouse, wheel, keyboard and touch input go to the page. Outside that window input is refused. See [Human takeover](/docs/guide/attention/). - **Timeline.** Every tool call with parameters, result, duration, error code and screenshot, plus navigations, vault access, blocked URLs and attention requests. Filter by kind, follow live, expand rows. When [telemetry](/docs/guide/telemetry/) is on, each call shows its `trace_id` and, with `--otelTraceUrlTemplate`, a link into your tracing backend. - **Vault.** This session's vault access log and pending confirmations. - **Identity.** What the browser presents to websites: user agent, client-hint brands, platform, Chrome version, locale, timezone, screen and viewport, and whether fingerprinting and humanized input are on. See [Stealth](/docs/guide/stealth/). - **Files.** Data directory path, `trace.zip` with size, download, and **Open trace viewer** (Playwright Trace Viewer with DOM snapshots, network and console, served by BrowserHive), screenshots, downloads, and the command to open the trace locally. ## Attention The human-in-the-loop queue. Each pending request shows the agent's reason, the mode (takeover or notify), how long the agent has waited, any options it attached, and a message box. **Resolve** or **Reject** sends your message back to the agent; **Open live & take over** jumps to the live view. Below the queue, the history of settled requests with outcome, wait time and who resolved them. ## Websites Every URL any agent visited, with the most visited domains, category tags for non-public destinations (IP addresses, local hosts, FTP), and filters by session, domain and time window. ## Blocklist The loaded rules and how often each one fired, lines in the file that do nothing (duplicates, refused patterns), attempts by source (tool boundary or network), sessions affected and top domains. **Reload blocklist** re-reads the file without a restart. When no blocklist is configured the page explains how to add one. ## Vault Available with `--vault bitwarden`. - **Status and unlock:** lock state, unlock by pasting a session token from `bw unlock --raw` (the dashboard never asks for your master password), sync. - **Confirmations:** fills waiting for your approval, with the requesting session, target URL and tool. Approve, or deny with a reason recorded in the audit log only. - **Origin tester:** type a URL and see which entries would fill there. - **Folders:** each vault folder has a policy (manual, allow all, reject all) and folder-wide flags. - **Bindings:** per entry, the allowed origins, authorized sessions and flags. Details in the [vault guide](/docs/guide/vault/). ## Vault log The audit trail of every `vault_fill` and denied listing: time, entry, result, origin check, whether `evaluate` was enabled, session and page URL. Filterable. It never contains secrets. ## Logs A live tail of the server log with level, module, session and trace filters, pause-on-scroll, and NDJSON export. Change the log level at runtime from the System page. ## System Version, transport, uptime, bind address, sessions live versus the cap, open attention requests, live views, dashboard connections, default persistence, whether `evaluate` is allowed, vault backend, blocklist, retention, database size and schema version, telemetry endpoint and data directory. A stealth section shows the stealth level, driver, fingerprint and humanize defaults and CAPTCHA mode. Banners warn when `evaluate` is enabled together with the vault, when disk space is low, or when a subsystem is degraded. The configuration table lists every effective setting with its source (`cli`, `file`, `env`, `default`, `derived`) and the values it shadowed; secrets are shown as redacted. ## Notifications Persisted notifications for attention requests, tool errors, crashed sessions and pending vault confirmations, grouped by day. Mark as read or dismiss, individually or all at once. --- # Configuration Source: https://browserhive.ai/docs/guide/configuration/ Every setting of BrowserHive is a **key** that you can set in three places: an environment variable, the config file, or a CLI flag. This page explains how they combine. The [configuration reference](/docs/reference/configuration/) lists every key with its type, default and constraints. ## Precedence Lowest to highest. **Rightmost wins.** ``` defaults < environment variables < browserhive.config.json < CLI flags ``` When a key comes from more than one source, the startup log says which value won: ``` config: maxSessions=8 (cli) shadows config-file=4, env=2 ``` `browserhive config show` prints the effective value and source of every key, and the dashboard's System page shows the same table. Secrets such as `authTokens` and `otelHeaders` are always shown as ``. ## Naming Each key has one camelCase name; the other spellings are derived from it: | Key | CLI flag | Environment variable | Config file | |---|---|---|---| | `maxSessions` | `--maxSessions 8` | `BROWSERHIVE_MAX_SESSIONS=8` | `"maxSessions": 8` | | `sessionLease` | `--sessionLease 2h` | `BROWSERHIVE_SESSION_LEASE=2h` | `"sessionLease": "2h"` | | `allowInsecureBind` | `--allowInsecureBind` | `BROWSERHIVE_ALLOW_INSECURE_BIND=true` | `"allowInsecureBind": true` | Rules: - CLI flags are case-sensitive camelCase. `--maxsessions` or any kebab-case spelling is an unknown flag; the error suggests the camelCase spelling. - Booleans: `--admin` sets true; `--admin=false` or `--noAdmin` sets false. `--admin false` (with a space) is not accepted, so a boolean never swallows the next argument. - Durations take `ms`, `s`, `m`, `h` or `d` (`30m`, `2h`). A bare number is milliseconds. - Sizes take `B`, `KiB`, `MiB`, `GiB`, `KB`, `MB` or `GB`. A bare number is bytes. - Lists are comma-separated on the CLI and in env (`--otelSignals traces,logs`), and JSON arrays in the file. - Maps are `k=v,k2=v2` on the CLI and in env, and JSON objects in the file. ## Fail fast Configuration problems stop the process before it binds a port or opens the database, with exit code `64` and a message that names the source: ``` browserhive: unknown flag '--maxSession'. Did you mean '--maxSessions'? Run 'browserhive --help'. browserhive: invalid value for --sessionLease: '2 hours'. Expected a duration like '2h', '30m', '90s', '500ms', or an integer of milliseconds. browserhive: BROWSERHIVE_PORT is set but empty. Unset it or provide a value. ``` Unknown `BROWSERHIVE_*` environment variables and unknown keys in the config file fail the same way. An empty value is an error, not "unset". Some combinations are rejected too, for example `humanize=true` with `stealth=off`, or `admin=true` with `transport=stdio`. The full list is under [cross-field rules](/docs/reference/configuration/#cross-field-rules). Check a configuration without starting the server: ```bash browserhive config validate ``` ## The config file `browserhive.config.json` is plain JSON (no comments). BrowserHive uses the first file it finds: 1. `--config ` (or `BROWSERHIVE_CONFIG`). A missing file here is an error. 2. `./browserhive.config.json` in the current directory. 3. `/browserhive.config.json`. Relative paths inside the file resolve against the file's directory. A config file found in the data directory may not change `dataDir`. For editor completion, write the JSON Schema next to your file and reference it: ```bash browserhive config schema > browserhive.schema.json ``` ```json { "$schema": "./browserhive.schema.json", "transport": "http", "host": "127.0.0.1", "port": 9876, "admin": true, "auth": "token", "persistence": "persistent", "maxSessions": 6, "sessionLease": "2h", "attentionTimeout": "6h", "minAttentionWait": "30m", "stealth": "standard", "humanize": true, "vault": "bitwarden", "blocklist": "./blocklist.txt", "blocklistWatch": true, "retentionDays": 14, "retentionBytes": "2GiB", "logLevel": "info,sessions=debug", "logFormat": "auto", "otel": true, "otelEndpoint": "http://127.0.0.1:4318", "otelServiceName": "browserhive-lab" } ``` The same schema is published in this repository as [config.schema.json](/docs/reference/config.schema.json). If the file contains `authTokens`, keep it readable only by you (`chmod 600`); `doctor` warns otherwise. Prefer the environment for secrets. ## Most used keys | Key | Default | Purpose | |---|---|---| | [`transport`](/docs/reference/configuration/#transport) | `http` | `http` or `stdio` | | [`host`](/docs/reference/configuration/#host), [`port`](/docs/reference/configuration/#port) | `127.0.0.1`, `9876` | bind address, shared by MCP, API, WebSocket and dashboard | | [`admin`](/docs/reference/configuration/#admin) | `false` | dashboard, REST API, live view, traces | | [`auth`](/docs/reference/configuration/#auth) | `off` | `token` requires bearer tokens on `/mcp` and scopes sessions to their owner | | [`persistence`](/docs/reference/configuration/#persistence) | `memory` | default for `launch_session`: `memory`, `persistent`, `storage-state` | | [`vault`](/docs/reference/configuration/#vault) | `off` | `bitwarden` enables credential injection | | [`stealth`](/docs/reference/configuration/#stealth) | `standard` | `off`, `standard` or `max`; see also `fingerprint` and `humanize` | | [`maxSessions`](/docs/reference/configuration/#maxSessions) | derived from RAM | concurrent session cap, or `unbounded` | | [`sessionLease`](/docs/reference/configuration/#sessionLease) | `2h` | idle sessions are closed after this long | | [`blocklist`](/docs/reference/configuration/#blocklist) | unset | URL blocklist file | | [`dataDir`](/docs/reference/configuration/#dataDir) | OS default | where state lives | | [`logLevel`](/docs/reference/configuration/#logLevel), [`logFormat`](/docs/reference/configuration/#logFormat) | `info`, `auto` | logging; `logLevel` accepts per-module levels like `info,sessions=debug` | | [`recordToolResults`](/docs/reference/configuration/#recordToolResults) | `full` | how much of each tool result is stored | | [`otel`](/docs/reference/configuration/#otel), [`otelEndpoint`](/docs/reference/configuration/#otelEndpoint) | `false` | OpenTelemetry export | `maxSessions` defaults to `min(floor(RAM in GiB / 1.5), 20)`. `trace` defaults to the value of `admin`, and `fingerprint` defaults to true only when `stealth=max`. ## Environment-only deployments For a service manager (systemd `EnvironmentFile`, `docker --env-file`): ```sh BROWSERHIVE_TRANSPORT=http BROWSERHIVE_HOST=0.0.0.0 BROWSERHIVE_PORT=9876 BROWSERHIVE_AUTH=token BROWSERHIVE_AUTH_TOKENS=ci-runner:REPLACE_WITH_32_PLUS_CHARS BROWSERHIVE_ADMIN=true BROWSERHIVE_DATA_DIR=/var/lib/browserhive BROWSERHIVE_LOG_FORMAT=json BROWSERHIVE_OTEL=true OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 ``` Standard `OTEL_*` variables are honoured below `BROWSERHIVE_*` ones; see [the reference](/docs/reference/configuration/#opentelemetry-environment-variables). ## Per-session settings are tool arguments An agent can override some defaults for one session through `launch_session` arguments: `persistence_mode`, `headless`, `channel`, `stealth`, `fingerprint`, `humanize`, `disable_evaluate`, `vault_enabled`, `context_options` and `launch_options`. These are not configuration and never appear in `config show`. See [`launch_session`](/docs/reference/tools/#launch_session). ## Runtime changes Most keys need a restart. `logLevel`, `logFormat` and `otelTraceUrlTemplate` can be changed while the server runs (the log level from the dashboard's System page or `PATCH /api/v1/system/log-level`). --- # Security model Source: https://browserhive.ai/docs/guide/security/ BrowserHive runs on your machine and treats **the agent as untrusted and the operator as trusted**. Websites are hostile. This page explains what protects what, and the limits you should know about. ## One port, one bind rule MCP, the REST API, the WebSocket and the dashboard share `--host` and `--port`. The default bind is `127.0.0.1`. Binding a non-loopback address (for example `--host 0.0.0.0`) is **refused** unless one of these is true: - `--auth token` is set, or - `--allowInsecureBind` is set, which you should only do on a network you fully control. The startup banner then shows a red warning. The refusal is [`INSECURE_BIND_REFUSED`](/docs/reference/errors/#INSECURE_BIND_REFUSED) with exit code 3. BrowserHive does not terminate TLS; for remote access, put a reverse proxy with TLS in front and list it in `--trustedProxies` so client addresses and `X-Forwarded-Proto` are honoured. Requests whose `Host` header does not match the bind (DNS rebinding) are rejected. ## Authentication ### Agents (`/mcp`) | `--auth` | Behaviour | |---|---| | `off` (default) | No credentials. Every caller is the principal `local` and sees every session. Loopback binds only. | | `token` | `Authorization: Bearer ` on every request, including `initialize`. A missing or unknown token gets HTTP 401. Each token belongs to a principal, and a principal only sees and controls the sessions, saved logins and attention requests it created. A reconnecting client with the same token keeps its sessions. | Tokens: - On the first start with `--auth token`, a token for the principal `agent-1` is generated and printed once. - `browserhive admin tokens create ` issues more; `list` shows name, public prefix, creation and last use; `revoke` is immediate. The dashboard's System page offers the same. - Tokens are stored as SHA-256 hashes with an 8-character public prefix for lookup; the plaintext is only shown at creation. - `BROWSERHIVE_AUTH_TOKENS=name:token,…` adds tokens (at least 32 characters) that are never stored. `--auth token` requires `--transport http`. Under stdio the only caller is the process that spawned BrowserHive. ### Operators (dashboard and REST API) The dashboard always requires a password, independent of `--auth`. 1. **Seed password.** On the first start with `--admin`, a 24-character password is generated from a cryptographic random source, printed once (never under stdio), and written to `/admin/credentials.txt` with mode `0600`. 2. **Forced change.** Until you set a new password (12 to 256 characters, different from the current one), only the change-password flow is reachable. After the change, the seed file is overwritten with zeros and deleted. 3. **Storage.** Passwords are hashed with Argon2id. 4. **Sessions.** Signing in sets an `HttpOnly`, `SameSite=Strict` cookie. Sessions expire after 15 minutes idle or 8 hours total. Changing the password signs out every other session. 5. **Recovery.** `browserhive admin reset-password` (with the server stopped) issues a new seed password. Operator API tokens let scripts call the REST API without a browser. Every login, logout, password change and token issue or revocation is recorded in an audit table and logged. ### Authorization Operators hold every scope. Agent tokens only hold the tool scope: they cannot read the dashboard API, resolve their own attention requests, or change vault policy. Scopes per REST route are listed in the [API reference](/docs/reference/api/). ## The vault: credentials the model never sees With `--vault bitwarden`, the agent calls `vault_fill` with an **entry name and CSS selectors**. BrowserHive fetches the credential from your vault and types it into the page. The password never appears in the tool call, the tool result, the logs or the database. Every fill passes these gates, in order, and each outcome writes exactly one audit row: 1. The session allows the vault (`vault_enabled`). 2. The caller is authorized for the entry: both the session slug and the calling principal must match the binding. 3. If the entry requires it, `evaluate` must be off for the session. 4. The page's origin is in the entry's allowed origins, matched on the registrable domain (so `evil-example.com` and `example.com.attacker.net` never match `*.example.com`). 5. If the entry requires confirmation, an operator approves it on the dashboard. 6. The credential is fetched and a redaction window opens before anything is typed. 7. The form's `action` must not post to another origin, checked before filling and before submitting. Failures are returned as a status and reason (`origin_mismatch`, `not_authorized`, …), never as the credential or backend error text. Unknown entries and unauthorized entries give the same answer, so an agent cannot probe what exists. See [the vault guide](/docs/guide/vault/). ## Redaction and its limits - **Redaction is substring-based.** For a few seconds after a fill (and until the page navigates off the entry's origins), exact occurrences of the password (and the username, if the entry says so) are replaced by `[REDACTED]` in tool results. An agent that base64-encodes a value bypasses it. It is a guardrail, not a boundary. - **`evaluate` can read the DOM.** After the window closes, an agent with `evaluate` can read a field that still holds the credential. Use `clear_after_fill: true`, `disable_evaluate: true` per session, `require_no_evaluate` per vault entry, or `--allowEvaluate false` server-wide, which makes every `evaluate` call fail with [`EVALUATE_DISABLED`](/docs/reference/errors/#EVALUATE_DISABLED). - **Traces do not contain typed credentials.** Playwright tracing is stopped before the first credential keystroke and restarted after submit, and the parts are merged when the session closes. The replay has a gap instead of the login POST. - **Pixels are not redacted.** The live view and screenshots show exactly what the browser shows. Screenshot tracing skips frames while a redaction window is open, but operators are trusted with the live view. - Every secret BrowserHive creates or handles (seed password, tokens, cookies, vault session tokens, OTLP headers) is registered with a redactor that scrubs logs, database rows, WebSocket frames, MCP results and OTLP exports. ## What is recorded Local-first means you own the records. With the defaults: | Recorded | Not recorded | |---|---| | Every tool call: name, arguments, timing, outcome, error code, trace id | Cookie **values**. Only name, domain, path and expiry are stored or shown. | | Tool results as text, capped at 16 KiB | URL query strings and fragments, unless the parameter is in `--urlQueryAllowlist` | | Navigations, blocked attempts, attention requests, vault access (without secrets) | Passwords, tokens, vault credentials, `Authorization` headers | | Screenshots taken by the agent; one frame per tool call with `--screenshotTrace` | Fields marked sensitive in the contracts, redacted before any sink | | A Playwright trace per session when `trace` is on (default with `--admin`) | Error messages are passed through the same redaction first | Stricter deployments can reduce what tool results store: | `--recordToolResults` | Stored | |---|---| | `full` (default) | the result text, capped at 16 KiB | | `shape` | keys and sizes only | | `none` | nothing from the result | Records are pruned after `--retentionDays` (default 7) or when the database and artifacts exceed `--retentionBytes` (default 1 GiB). `browserhive purge` deletes local state on demand. ## Files on disk The data directory is `0700` and secret files are `0600`. Saved logins (`auth-states/`) contain real cookies and storage; protect them like passwords. They are protected by your OS user account, not encrypted by BrowserHive. ## The blocklist is not an egress firewall `--blocklist` stops navigations at the tool boundary and aborts document requests in the browser. Subresources still load, and an agent with `evaluate` can `fetch()` a blocked URL. Use it to keep agents off pages, not to contain them. ## Takeover is full control During an open attention request, an operator's mouse and keyboard input goes straight to the agent's browser. Input is re-checked against the open request on every message and refused otherwise. ## Other limits - WebAuthn and passkeys cannot be replayed from saved state. - Telemetry is off by default. Nothing leaves the host unless you set `--otel`, and then only to the endpoint you configure. Report vulnerabilities as described in `SECURITY.md` at the root of the repository. --- # Vault: logins the model never sees Source: https://browserhive.ai/docs/guide/vault/ The vault lets an agent log in to websites without ever handling the password. The agent names an entry and the form fields; BrowserHive fetches the credential from your password manager, checks that it is allowed on this page, types it, and returns only a status. Bitwarden is the supported backend. Read [the security model](/docs/guide/security/#the-vault-credentials-the-model-never-sees) for the full list of gates and limits. ## 1. Set up Bitwarden Install the [Bitwarden CLI](https://bitwarden.com/help/cli/) so `bw` is on `PATH`, then log in once: ```bash bw login ``` For a self-hosted server, run `bw config server https://vault.example.com` first. BrowserHive never asks for your master password: you unlock `bw` yourself and give BrowserHive only the session token. ## 2. Start BrowserHive with the vault ```bash browserhive --admin --vault bitwarden ``` The vault works without `--admin`, but then you cannot unlock, bind entries or approve confirmations from the dashboard. `browserhive doctor` checks that `bw` is on `PATH`. ## 3. Unlock The vault starts locked unless a session token is already available. First create a session token in your own terminal (as the same user that runs BrowserHive, so `bw` finds the same login): ```bash bw unlock --raw ``` `bw` asks for your master password and prints the token. Then either: - **Dashboard → Vault → Unlock:** paste the token into **Session token**. BrowserHive checks it with `bw status` and keeps it in memory only. - Export it before starting the server: `export BW_SESSION="$(bw unlock --raw)"`, then `browserhive --admin --vault bitwarden`. If unlock fails with [`VAULT_UNLOCK_FAILED`](/docs/reference/errors/#VAULT_UNLOCK_FAILED), the token has expired or belongs to another login (for example after `bw lock` or `bw logout`). Run `bw unlock --raw` again and paste the new token. **Lock** discards the token. **Sync** pulls changes from the Bitwarden server. While the vault is locked, `vault_fill` fails with [`VAULT_LOCKED`](/docs/reference/errors/#VAULT_LOCKED), which tells the agent to wait for an operator. BrowserHive runs `bw` with a minimal environment (`PATH`, `HOME`, `BW_SESSION`), passes entry names after `--` so they can never be read as options, and applies a timeout to every call. ## 4. Decide what agents may use Nothing is fillable until you allow it. There are two levels, both on the dashboard's Vault page. ### Folder policies Each Bitwarden folder (plus "no folder") has an access mode: | Mode | Effect | |---|---| | `manual` (default) | Only entries with an explicit binding are fillable. | | `allow_all` | Every entry in the folder with at least one login URI is fillable. Its allowed origins are the hostnames of its URIs. | | `reject_all` | Nothing in the folder is fillable, even with a binding. | An `allow_all` policy also sets who may use it (all sessions, or session slug globs such as `shop-*`) and folder-wide flags (below). If two entries in an `allow_all` folder have the same name, neither is fillable until you rename one or bind it manually. ### Bindings A binding makes one entry fillable under a stable **handle** (for example `work.github`), which is the `entry_name` agents pass. Each binding has: | Field | Meaning | |---|---| | Allowed origins | Where the credential may be typed. `github.com` matches that hostname; `*.example.com` matches `example.com` and every subdomain, compared on the registrable domain; `example.com/login*` restricts the path. Ports are ignored. | | Authorized sessions | Session slug globs (`shop-*`), or all sessions. | | Authorized principals | With `--auth token`, which agent tokens may use it; empty means any. Both the slug and the principal must match. | | `dashboard_confirm` | Every fill waits for an operator to approve it on the dashboard. | | `require_no_evaluate` | The fill is refused unless `evaluate` is disabled for the session. | | `redact_username` | The username is redacted from tool results too, not only the password. | Use **origin tester** on the Vault page to check which entries would fill on a given URL for a given session slug. Bindings and policies are stored in the database. **Export** and **Import** (merge or replace) move them as JSON, for backups or editing by hand. Edits use optimistic concurrency: if someone else changed a binding since you loaded it, saving returns [`CONFLICT`](/docs/reference/errors/#CONFLICT) and the dashboard reloads it. ## 5. What the agent does List the entries usable on the current page. Passing the domain the agent believes it is on lets BrowserHive catch a page that is not what the agent thinks: ```jsonc vault_list_available({ "session_id": "shop-a1b2c3d4", "url": "github.com" }) // → { "entries": [{ "entry_name": "work.github", "allowed_origins": ["github.com"], "redact_username": false, "require_no_evaluate": false }], // "scope": "page", "scoped_to": "github.com" } ``` The listing never reveals which entries require confirmation or which sessions are authorized. If the declared domain does not match the real page, the result is empty with `scope: "rejected"`, and the attempt is audited. Fill: ```jsonc vault_fill({ "session_id": "shop-a1b2c3d4", "entry_name": "work.github", "username_selector": "#login_field", "password_selector": "#password", "submit_selector": "input[type=submit]", "clear_after_fill": true }) // → { "status": "success", "redacted": true } ``` Failures are returned, not thrown: | `status` | `reason` examples | |---|---| | `blocked` | `vault_disabled`, `not_authorized`, `evaluate_required_off`, `dashboard_denied`, `confirm_timeout`, `form_action_mismatch` | | `origin_mismatch` | the page is not on an allowed origin | | `auth_failed` | `entry_not_found`, `backend_error`, `fill_failed`, `submit_failed` | Only [`VAULT_NOT_CONFIGURED`](/docs/reference/errors/#VAULT_NOT_CONFIGURED) and [`VAULT_LOCKED`](/docs/reference/errors/#VAULT_LOCKED) are raised as errors. `clear_after_fill` defaults to `false`: the credential stays in the form unless the page navigates away. ## 6. Confirmations For entries with `dashboard_confirm`, the fill blocks until an operator decides. The request appears on the Vault page, on the session's page and as a notification, with the session, target URL and requesting tool. **Approve** lets the fill continue; **Deny** returns `blocked` / `dashboard_denied` to the agent. A deny reason is written to the audit log only, never sent to the agent. A confirmation that nobody answers within `--attentionTimeout` (default 6 h) is denied with `confirm_timeout`. Closing the session or disconnecting the agent cancels it. Under `--transport stdio` there is no dashboard, so these entries are always denied. ## 7. Audit Every fill and every rejected listing writes one row to the **Vault log**: time, entry, result, origin check, whether `evaluate` was enabled, session, page URL and the reason. It never contains credentials. ## Hardening checklist - Prefer `manual` folders and narrow origins. - Use `require_no_evaluate` for sensitive entries, or run with `--allowEvaluate false`. - Use `clear_after_fill: true` in your agent's prompts. - Use `dashboard_confirm` for anything that moves money. - Remember that the live view and screenshots show raw pixels. --- # Human takeover (attention requests) Source: https://browserhive.ai/docs/guide/attention/ Sometimes an agent needs a person: a CAPTCHA, a two-factor prompt, a judgment call. The `request_attention` tool asks an operator for help and **blocks** until the operator answers, the request times out, or it is cancelled. While it is open, the operator can watch the agent's browser and drive it directly. Attention requests need the HTTP transport and the dashboard: ```bash browserhive --admin ``` Under `--transport stdio`, `request_attention` and `get_attention_result` return [`ATTENTION_REQUIRES_HTTP`](/docs/reference/errors/#ATTENTION_REQUIRES_HTTP). ## The agent's side ```jsonc request_attention({ "session_id": "shop-a1b2c3d4", "reason": "Checkout shows a CAPTCHA. Please solve it and resolve this request.", "mode": "takeover", "max_wait_seconds": 1800 }) // blocks, then → { "status": "resolved", "message": "Done, continue.", "resolved_by": "admin", "resolved_at": 1768000000000, "request_id": "a-…" } ``` | Parameter | Meaning | |---|---| | `reason` | What you need; shown to the operator. | | `mode` | `takeover` (default): the operator may drive the browser. `notify`: view only. Both block. | | `options` | Any JSON, shown as-is to the operator (for example choices to pick from). | | `max_wait_seconds` | `0` or absent: wait up to the server cap. Positive: at least the server floor, never above the cap. | Outcomes: | `status` | When | |---|---| | `resolved` | The operator resolved it; `message` carries their reply. | | `rejected` | The operator rejected it, or the session closed, crashed, expired, or the server restarted or shut down (the message says which). | | `timeout` | Nobody answered in time: "Attention request timed out; the operator was not available to respond." | | `cancelled` | The client cancelled the call or disconnected. | `message` and `resolved_by` are omitted when there is nothing to report. ### Waiting and reconnecting - The server cap is `--attentionTimeout` (default `6h`). The floor is `--minAttentionWait` (default `30m`); a smaller `max_wait_seconds` is raised to it so a human has time to respond, and the tool's description tells the agent the floor. `--minAttentionWait 0` disables the floor. - While waiting, the server sends an MCP progress notification every 25 seconds so clients and proxies keep the call alive. - If the connection drops, the agent can call `get_attention_result({ "request_id": "a-…" })`. It returns immediately when the request is settled and otherwise blocks the same way. An unknown id, or one owned by another principal, returns `rejected` with "Unknown attention request". - While a request is open, the session's idle lease is paused, so it is not closed for inactivity. ## The operator's side A new request shows up as a notification, on the **Attention** page, and as a banner on the session's page. 1. Open the request. You see the reason, mode, options and how long the agent has been waiting. 2. For `takeover` requests, click **Open live & take over**. The live view accepts your mouse, wheel, keyboard and touch input and sends it to the agent's browser. Input is allowed only while this request is open and is checked on every event. 3. Type a message for the agent and click **Resolve** (done, continue) or **Reject** (the agent should give up or try something else). Bulk resolve and reject are available when several requests are waiting. Settled requests stay in the history with outcome, wait time and who answered. **Resize agent browser** on the live view changes the agent's real viewport. It is an observability control and does not need an open attention request. ## CAPTCHAs `--captcha attention` (the default) documents the intended flow: an agent that hits a CAPTCHA calls `request_attention` and an operator solves it. BrowserHive does not detect or solve CAPTCHAs itself. Without `--admin` there is nobody to hand the page to; explicitly setting `--captcha attention` without `--admin` is a configuration error. `--captcha off` states that no hand-off is expected. ## Notifications and limits - Each open request produces a persisted dashboard notification. - At most 8 open operator requests per session and 256 in total; beyond that the call fails with [`RATE_LIMITED`](/docs/reference/errors/#RATE_LIMITED). - On restart, requests that were pending become `rejected` ("Attention request was lost when the server restarted."). --- # Stealth Source: https://browserhive.ai/docs/guide/stealth/ Stealth makes a BrowserHive session report what a real Chrome on your machine would report, and optionally makes input look hand-made. It is **not** invisibility. This page says what it does, what it does not do, and where the ceilings are. The principle: **assert nothing rather than assert something wrong.** BrowserHive never invents an operating system, GPU, timezone or location. Everything it presents is derived from the real host. ## Levels | `--stealth` | What you get | |---|---| | `off` | Stock Playwright Chromium. `navigator.webdriver` is `true`. No identity override. | | `standard` (default) | The full Chromium binary in new-headless mode, the Patchright driver when installed, automation flags removed, and a coherent identity applied through the DevTools protocol. | | `max` | `standard` plus a display fingerprint (`fingerprint` defaults to `true`). | Related keys: | Key | Default | Effect | |---|---|---| | `--stealthDriver` | `auto` | `auto` uses Patchright when installed, else Playwright. `patchright` requires it (startup fails with [`BROWSER_NOT_INSTALLED`](/docs/reference/errors/#BROWSER_NOT_INSTALLED) if missing). `playwright` never uses it. | | `--fingerprint` | `true` only with `max` | Coherent screen and window geometry per session. Requires `standard` or `max`. | | `--humanize` | `false` | Human-like pointer paths and typing rhythm. Requires `standard` or `max`. | | `--defaultHeadless` | `true` | Default for `launch_session` `headless`. | | `--defaultChannel` | `chromium` | Default browser: `chromium`, `chrome` or `edge`. | An agent can override `stealth`, `fingerprint`, `humanize`, `headless` and `channel` per session in [`launch_session`](/docs/reference/tools/#launch_session). The dashboard's **Identity** tab shows what each session actually presents, and `list_sessions` / `session_info` return it as `identity`. ## What `standard` does - **Real browser binary.** `chromium` runs the full Chromium build, never the stripped headless shell. That restores `window.chrome`, plugins, MIME types, the PDF viewer flag and hardware WebGL. - **Automation signals removed.** `navigator.webdriver` is `false`; the `--enable-automation` switch is not passed. - **Patchright** (when installed by `browserhive init`) patches the remaining automation leaks at launch. `evaluate` still runs in the page's main world. - **One coherent identity**, set through the DevTools protocol on every page, including new tabs and popups before their first request: - User agent: the real one, with `HeadlessChrome` rewritten to `Chrome`. - Client hints: brands `Chromium` and `Google Chrome` with the real version, platform and architecture from the host, platform version from the OS release. - `Accept-Language` from the host locale (`en-CA` → `en-CA,en`), and `navigator.deviceMemory` when the browser would report none. - **Locale and timezone** from the host (`LC_ALL`, `LANG`, the system timezone). No coordinates are ever set. - `set_extra_http_headers` refuses to change `user-agent`, `accept-language` and `sec-ch-ua*` on stealth sessions, so the identity stays consistent. ## What `fingerprint` adds A believable screen for headless sessions: a display size from a catalogue that matches the host OS family (for example 1512×982 at 2× on macOS, 1920×1080 on Windows and Linux; never Playwright's default 1280×720), with a window size and position derived from it so that `innerHeight < outerHeight ≤ availHeight ≤ screen.height` always holds. The patched getters report themselves as native code. It is seeded per session. A profile saved with `save_full_profile` keeps its seed, so restoring it brings back the same screen with the same cookies. Hardware concurrency, WebGL vendor and renderer, canvas, audio and fonts are left native on purpose: they are cross-checkable against the real GPU. Downgrades, each recorded as a session warning: - A headed session gets locale and timezone only, because its window is really on screen. - A caller-supplied `viewport` keeps the geometry unasserted ([`VIEWPORT_OVERRIDE_UNASSERTED`](/docs/reference/errors/#VIEWPORT_OVERRIDE_UNASSERTED)). - A proxy passed in `launch_options` or `context_options` disables the host locale and timezone, because host values over someone else's exit IP are worse than none ([`BYO_PROXY_UNSEEDED`](/docs/reference/errors/#BYO_PROXY_UNSEEDED)). - A caller-supplied `locale` or `timezoneId` is honoured. If applying the identity fails, the session continues without it and records [`STEALTH_INIT_FAILED`](/docs/reference/errors/#STEALTH_INIT_FAILED). ## What `humanize` adds `click`, `hover`, `type_text`, `scroll` (by offset) and vault typing use curved pointer paths with overshoot, Fitts-law timing, press dwell, and typing with varied intervals, word pauses and occasional corrected typos. It is seeded per session, so each session has one consistent "hand". Long text (over 400 characters) and calls whose timeout budget is too small fall back to native input, so timeouts keep working. `fill`, `select_option` and `drag_and_drop` stay native. ## Launch-argument guard To protect isolation, `launch_options.args` may not contain `--user-data-dir`, `--profile-directory`, `--disk-cache-dir`, `--no-sandbox`, `--disable-setuid-sandbox`, `--disable-web-security`, `--disable-site-isolation-trials`, `--disable-features`, `--single-process`, `--no-zygote` or the remote-debugging switches. `chromiumSandbox: false`, `env`, `downloadsPath` and `recordVideo` are refused too. Violations fail with [`UNSAFE_LAUNCH_ARG`](/docs/reference/errors/#UNSAFE_LAUNCH_ARG). An `executablePath` override is allowed but disables channel routing ([`EXECUTABLE_PATH_OVERRIDE`](/docs/reference/errors/#EXECUTABLE_PATH_OVERRIDE)). ## Proxies There is no managed proxy support. Bring your own through Playwright's options: ```jsonc launch_session({ "slug": "geo", "launch_options": { "proxy": { "server": "http://proxy.example:3128", "username": "u", "password": "p" } } }) ``` Loopback and private network ranges are always bypassed so local tooling never goes through the proxy. The session records a `proxy_label` (never credentials). Chromium's raw proxy switches are also accepted in `args`. A managed pool with rotation and exit-IP geolocation is planned; the configuration key `proxy` is reserved for it and rejected today. ## Ceilings These are known and documented, not bugs: - **TLS fingerprints** (JA3/JA4) cannot be changed through Playwright. They are already close to Chrome's. - **Out-of-process iframes** are not covered by the user-agent override; cross-origin frames may report the raw user agent. - **The hardest bot checks** (for example Cloudflare Turnstile in strict mode) can still detect DevTools-protocol automation and synthetic input. Use [human takeover](/docs/guide/attention/) for them. - **Canvas, audio and font entropy** are the host's own: a stable identity tied to your machine, not a rotating one. - **GREASE brand and platform version** are plausible values, not byte-exact copies of the installed Chrome. - **WebAuthn and passkeys** cannot be replayed from saved state. - **Firefox and WebKit** are not supported. Stealth is a capability for legitimate automation of sites you are allowed to use. Respect their terms. --- # Telemetry (OpenTelemetry) Source: https://browserhive.ai/docs/guide/telemetry/ Telemetry is **off by default**, and nothing leaves your machine unless you turn it on. With `--otel`, BrowserHive exports traces, metrics and logs over OTLP/HTTP to any compatible backend: an OpenTelemetry Collector, Grafana Tempo/Loki/Mimir, Jaeger, Honeycomb, Datadog Agent and others. ```bash browserhive --admin --otel --otelEndpoint http://127.0.0.1:4318 --otelServiceName browserhive-dev ``` Or with standard OpenTelemetry variables: ```bash BROWSERHIVE_OTEL=true OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 browserhive --admin ``` ## Settings | Key | Default | Meaning | |---|---|---| | [`otel`](/docs/reference/configuration/#otel) | `false` | Turn export on. | | [`otelEndpoint`](/docs/reference/configuration/#otelEndpoint) | `http://127.0.0.1:4318` | OTLP/HTTP base URL; `/v1/traces`, `/v1/metrics`, `/v1/logs` are appended. Also `OTEL_EXPORTER_OTLP_ENDPOINT`. | | [`otelProtocol`](/docs/reference/configuration/#otelProtocol) | `http/protobuf` | or `http/json`. Also `OTEL_EXPORTER_OTLP_PROTOCOL`. | | [`otelHeaders`](/docs/reference/configuration/#otelHeaders) | none | `k=v,k2=v2`, for example an API key. Secret. Also `OTEL_EXPORTER_OTLP_HEADERS`. | | [`otelServiceName`](/docs/reference/configuration/#otelServiceName) | `browserhive` | `service.name`. Also `OTEL_SERVICE_NAME`. | | [`otelSampleRatio`](/docs/reference/configuration/#otelSampleRatio) | `1` | Parent-based trace sampling ratio. Also `OTEL_TRACES_SAMPLER_ARG`. | | [`otelSignals`](/docs/reference/configuration/#otelSignals) | `traces,metrics,logs` | Which signals to export. | | [`otelVerbose`](/docs/reference/configuration/#otelVerbose) | `false` | Also export database query and DevTools-protocol command spans. | | [`otelTraceUrlTemplate`](/docs/reference/configuration/#otelTraceUrlTemplate) | unset | Dashboard deep link; `{trace_id}` is substituted. | `BROWSERHIVE_*` variables win over `OTEL_*` ones. The `otelEndpoint`, `otelProtocol`, `otelHeaders`, `otelServiceName` and `otelSampleRatio` keys are rejected unless `otel=true`, so a typo cannot silently do nothing. When export fails five times in a row the System page shows a degradation; serving is never affected. `browserhive doctor` checks that the endpoint is reachable. ## What is exported Resource attributes: `service.name`, `service.version`, `service.instance.id`, `host.name`, `os.type`, `browserhive.transport`. ### Traces One trace per tool call, rooted at `mcp.tool_call` with `browserhive.tool`, `browserhive.session_id`, `browserhive.principal`, `browserhive.event_id`, `browserhive.ok` and `browserhive.error_code`. Children cover what the call did: `session.create` with one span per creation phase (validate, admit, reserve, prepare profile, resolve identity, launch, install policies, start tracing, apply identity, register), `browser.launch`, `page.navigate` (sanitized URL, status code), `vault.fill` and its steps, `attention.wait`, and with `--otelVerbose` also `db.query` and `cdp.command`. HTTP requests (`http.request`), WebSocket commands (`ws.command`), migrations (`db.migrate`) and background sweeps are traced too. If the MCP client sends a W3C `traceparent` in the tool call's `_meta`, the tool span joins the client's trace. HTTP requests adopt an inbound `traceparent` and return one. ### Metrics | Instrument | Type | Attributes | |---|---|---| | `browserhive.tool_calls` | counter | `tool`, `ok`, `error_code` | | `browserhive.tool_call.duration` | histogram (ms) | `tool` | | `browserhive.sessions.active` | up-down counter | `state` | | `browserhive.session.launch.duration` | histogram (ms) | `channel`, `stealth` | | `browserhive.session.lifetime` | histogram (ms) | `closed_reason` | | `browserhive.ws.connections` | up-down counter | | | `browserhive.ws.buffered_bytes` | gauge | `connection_id` | | `browserhive.ws.frames_dropped` | counter | `channel` | | `browserhive.db.write_queue.depth` | gauge | | | `browserhive.db.dropped_writes` | counter | `table` | | `browserhive.db.size_bytes` | gauge | | | `browserhive.browser.rss_bytes` | gauge | `session_id` | | `browserhive.attention.open` | up-down counter | `kind` | | `browserhive.attention.wait` | histogram (ms) | `status` | | `browserhive.vault.fills` | counter | `result` | | `browserhive.blocklist.hits` | counter | `source` | | `browserhive.retention.pruned_rows` | counter | `table` | | `browserhive.process.*` | gauges | rss, heap, event-loop lag | Metrics are exported every 30 seconds. ### Logs Every log record, with `trace_id`, `span_id`, `request_id`, `session_id` and `principal` attached, so logs and traces correlate in your backend. Secrets are scrubbed before export, exactly as for local logs and the database. See [Security](/docs/guide/security/#redaction-and-its-limits). ## Trace deep links from the dashboard Every timeline row on a session page shows its `trace_id`. Set `--otelTraceUrlTemplate` and it becomes a link into your tracing UI: | Backend | Template | |---|---| | Jaeger | `http://localhost:16686/trace/{trace_id}` | | Grafana Tempo | a Grafana Explore URL for your Tempo data source, with `{trace_id}` as the query; copy one from Grafana ("Share → Copy link") and replace the trace id with `{trace_id}` | | Honeycomb | `https://ui.honeycomb.io//environments//trace?trace_id={trace_id}` | The template can be changed without a restart. ## A local stack in one directory Grafana with Tempo (traces), Loki (logs) and Prometheus (metrics) behind an OpenTelemetry Collector. Create these files in an empty directory and run `docker compose up -d`. `docker-compose.yml`: ```yaml services: otel-collector: image: otel/opentelemetry-collector-contrib:latest command: ["--config=/etc/otelcol-contrib/config.yaml"] volumes: ["./otel-collector.yaml:/etc/otelcol-contrib/config.yaml:ro"] ports: ["4318:4318"] depends_on: [tempo, loki] tempo: image: grafana/tempo:latest command: ["-config.file=/etc/tempo.yaml"] volumes: ["./tempo.yaml:/etc/tempo.yaml:ro"] loki: image: grafana/loki:latest command: ["-config.file=/etc/loki/local-config.yaml"] prometheus: image: prom/prometheus:latest volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml:ro"] grafana: image: grafana/grafana:latest environment: GF_AUTH_ANONYMOUS_ENABLED: "true" GF_AUTH_ANONYMOUS_ORG_ROLE: Admin volumes: ["./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml:ro"] ports: ["3000:3000"] depends_on: [tempo, loki, prometheus] ``` `otel-collector.yaml`: ```yaml receivers: otlp: protocols: http: endpoint: 0.0.0.0:4318 processors: batch: {} exporters: otlp/tempo: endpoint: tempo:4317 tls: insecure: true otlphttp/loki: endpoint: http://loki:3100/otlp prometheus: endpoint: 0.0.0.0:8889 service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp/tempo] logs: receivers: [otlp] processors: [batch] exporters: [otlphttp/loki] metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] ``` `tempo.yaml`: ```yaml server: http_listen_port: 3200 distributor: receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 storage: trace: backend: local local: path: /var/tempo/traces wal: path: /var/tempo/wal ``` `prometheus.yml`: ```yaml scrape_configs: - job_name: otel-collector scrape_interval: 15s static_configs: - targets: ["otel-collector:8889"] ``` `grafana-datasources.yaml`: ```yaml apiVersion: 1 datasources: - name: Prometheus type: prometheus uid: prometheus url: http://prometheus:9090 isDefault: true - name: Tempo type: tempo uid: tempo url: http://tempo:3200 jsonData: tracesToLogsV2: datasourceUid: loki filterByTraceID: true - name: Loki type: loki uid: loki url: http://loki:3100 ``` Then: ```bash browserhive --admin --otel --otelServiceName browserhive-dev ``` Open Grafana at `http://localhost:3000` → Explore. Pick **Tempo** and search for service `browserhive-dev` to see tool-call traces, **Loki** for logs, **Prometheus** for metrics. The Collector's Prometheus exporter converts metric names to Prometheus style, for example `browserhive.tool_calls` becomes `browserhive_tool_calls_total`. Jaeger works the same way with less setup: `docker run -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest` accepts OTLP/HTTP on 4318 directly (traces only). --- # Command line Source: https://browserhive.ai/docs/guide/cli/ ``` browserhive [serve] [flags] start the server (default command) browserhive init [flags] install browsers, create the data dir, verify the host browserhive doctor [flags] diagnose the host and configuration browserhive purge [flags] delete local state (inventory, then confirmation) browserhive config show|schema|validate browserhive db status|backup|restore |migrate browserhive admin reset-password browserhive admin tokens list|create |revoke browserhive version | --version | -v browserhive help [command] | --help | -h ``` If the first argument is not a command word, `serve` is assumed and every argument is a flag. Each command accepts only its own flags; passing a server flag to `purge` is a usage error. `--help` wins over everything, `--version` over everything except `--help`, and `--` ends flag parsing. Help text is generated from the configuration schema, so `browserhive --help` always lists every server flag with its type, default and environment variable. Flags are camelCase; see [Configuration](/docs/guide/configuration/). ## `serve` Resolves the configuration, opens storage (migrating the database if needed), starts the listener and prints the banner: ``` BrowserHive 0.1.0 · bun 1.4.2 · patchright 1.63.0 MCP http://127.0.0.1:9876/mcp (auth: token) Dashboard http://127.0.0.1:9876/ (admin) Data dir /home/me/.local/share/browserhive (db v1, 12 MiB, 3 backups) Config env:2 file:/home/me/browserhive.config.json:9 cli:1 Sessions cap 8 (derived from 12 GiB RAM) · lease 2h · persistence memory Stealth standard · patchright · humanize on · fingerprint off Telemetry otel → http://127.0.0.1:4318 (http/protobuf) Vault bitwarden (locked) config: maxSessions=8 (cli) shadows config-file=4 Press Ctrl-C to stop. ``` The first-run dashboard password and the first agent token are printed once in this banner and never logged. `Ctrl-C` (or `SIGTERM`) stops gracefully within `--shutdownTimeout`: listeners, then sessions (traces are finalized), then storage. A second `Ctrl-C` exits immediately with code 130. Output streams: under `--transport stdio`, stdout carries only MCP frames and everything else goes to stderr. Under HTTP, the banner and pretty logs go to stdout on a terminal, and JSON logs go to stderr, so `browserhive 2> logs.jsonl` captures them. Colour is on for terminals; `NO_COLOR` or `--color never` disables it. All server flags are in the [configuration reference](/docs/reference/configuration/). ## `init` One-time setup, safe to re-run: 1. Create the data directory (`0700`) and subdirectories. 2. Install Chromium for Playwright (honours `PLAYWRIGHT_BROWSERS_PATH`). 3. Install Patchright's Chromium when `stealthDriver` is `auto` or `patchright`. 4. Create or migrate the database. 5. Print next steps. | Flag | Meaning | |---|---| | `--browsers chromium` | Browsers to install (only `chromium` today; Chrome and Edge channels use the OS installation). | | `--force` | Re-download even if present. | | `--dataDir `, `--config ` | Where state and configuration live. | | `--stealthDriver ` | `playwright` skips the Patchright download. | | `--writeSchema` | Write `browserhive.schema.json` next to a discovered config file. | ## `doctor` Checks Bun, browsers, data directory permissions and disk space, configuration, port availability, `bw` when the vault is on, the database and pending migrations, the OTLP endpoint when telemetry is on, `maxSessions` against RAM, and config-file permissions when it holds secrets. It also warns when the data directory contains an unrecognised data file, which BrowserHive neither reads nor migrates. `--json` prints `[{ check, status, detail }]`. Exit codes: `0` all good, `2` warnings only, `1` a check failed. ## `purge` Deletes local state after showing what will go: row counts per table, directory sizes and absolute paths. | Flag | Meaning | |---|---| | (none) | Delete the database (with its WAL files) and `sessions/`. You type `YES` to confirm. | | `--all` | Also delete saved logins, uploads, backups and the admin credentials. Vault bindings and policies are lost with the database. Asks for a second `YES`. | | `--dryRun` | Print the inventory and exit. | | `--yes` | Skip the prompts. Without a terminal and without `--yes`, `purge` refuses. | Stop the server first; `purge` warns if the database still lists open sessions. ## `config` | Command | Output | |---|---| | `config show [--json]` | Every key with its effective value, source and shadowed values. Secrets are ``. `--json` prints `{ key: { value, source, shadowed } }`. | | `config schema` | The JSON Schema for `browserhive.config.json`. | | `config validate [--config ]` | Runs the full resolver and prints exactly what `serve` would; exit `64` on error. | ## `db` | Command | Meaning | |---|---| | `db status` | Schema version, oldest compatible reader version, pending migrations, size, last backup, integrity check. | | `db backup [--out ]` | Consistent copy of the database (`VACUUM INTO`). | | `db restore [--yes]` | Replace the database with a backup. Refuses while a server is running, checks the file is a BrowserHive database, and backs up the current file first. | | `db migrate [--dryRun]` | Apply pending migrations (normally done by `serve`). | See [Upgrading](/docs/guide/upgrading/). ## `admin` | Command | Meaning | |---|---| | `admin reset-password` | New seed password for the dashboard, printed once and written to `admin/credentials.txt` (`0600`); a change is forced at next login. Refuses while the server is running. | | `admin tokens list [--json]` | Agent bearer tokens: name, public prefix, created, last used. | | `admin tokens create [--json]` | Issue a token. The plaintext is shown only now. | | `admin tokens revoke ` | Revoke immediately. | Token commands work on the database directly when the server is stopped, or through the REST API of a running server with `--url ` and an operator credential. ## `version` ``` browserhive 0.1.0 (bun 1.4.2, sqlite 3.53.2, playwright 1.63.0, patchright 1.63.0) ``` `--json` for scripts. ## Exit codes | Code | Meaning | |---|---| | `0` | Success, or clean shutdown after a signal. | | `1` | Fatal runtime error (a boot phase failed after configuration, storage corruption, `purge` or `doctor` failure). | | `2` | `doctor` found warnings only. | | `3` | Policy refusal at startup: [`INSECURE_BIND_REFUSED`](/docs/reference/errors/#INSECURE_BIND_REFUSED), [`ADMIN_REQUIRES_HTTP`](/docs/reference/errors/#ADMIN_REQUIRES_HTTP), [`BLOCKLIST_LOAD_FAILED`](/docs/reference/errors/#BLOCKLIST_LOAD_FAILED), [`PORT_IN_USE`](/docs/reference/errors/#PORT_IN_USE), [`DB_NEWER_THAN_BINARY`](/docs/reference/errors/#DB_NEWER_THAN_BINARY). Printed as `[CODE] message`. | | `64` | Usage error: unknown flag or key, invalid value, conflicting settings, missing argument. | | `130` | A second interrupt arrived before graceful shutdown finished. | --- # Programmatic API Source: https://browserhive.ai/docs/guide/programmatic-api/ Embed BrowserHive in your own Bun program, test harness or service with `createServer`. It uses the same configuration schema and startup sequence as the CLI. ```ts import { createServer } from 'browserhive'; const server = await createServer({ transport: 'http', host: '127.0.0.1', port: 9876, admin: true, auth: 'token', vault: 'bitwarden', sessionLease: '2h', }); await server.listen(); // resolves when the server is ready (/health reports ready) console.log(server.url); // http://127.0.0.1:9876 // ... await server.stop(); // graceful and idempotent ``` The module must run under Bun. ## Options `createServer(options)` accepts every [configuration key](/docs/reference/configuration/) in camelCase, **typed**: `port: 9876`, `admin: true`, `sessionLease: '2h'` or `sessionLease: 7_200_000`, `maxSessions: 4` or `'unbounded'`. Values are validated by the same schema as the CLI, and the options object takes the place of CLI flags in the precedence ladder. Extra fields: | Option | Default | Meaning | |---|---|---| | `env` | `process.env` | Environment to read `BROWSERHIVE_*` and `OTEL_*` from. Pass `{}` to ignore the real environment. | | `configFile` | discovery | A path, or `false` to ignore config files. | | `output` | process stdout/stderr | Where the banner and CLI-style output go. | | `logger` | built-in | An external log sink. | | `name` | `browserhive` | MCP `serverInfo.name`, for embedders. | `port: 0` picks a free port (programmatic API only), which is convenient in tests; read the real URL from `server.url` after `listen()`. ## Errors `createServer` resolves and validates the configuration eagerly and throws a configuration error with the same message the CLI would print (unknown key, invalid value, conflicting settings, insecure bind). `listen()` rejects with a typed error such as [`PORT_IN_USE`](/docs/reference/errors/#PORT_IN_USE) or [`DB_NEWER_THAN_BINARY`](/docs/reference/errors/#DB_NEWER_THAN_BINARY), after undoing whatever it had started. ## The server object | Member | Meaning | |---|---| | `url` | Base URL of the listener. | | `config` | The effective configuration, deeply frozen. | | `provenance` | Where each value came from and what it shadowed. | | `listen()` | Start. Idempotent. | | `stop(deadline?)` | Stop gracefully within the optional deadline. Idempotent, and safe after a failed `listen()`. | ## Exported types The package also exports `ServerOptions`, `BrowserHiveServer`, the tool names (`ALL_TOOL_NAMES`), `ERROR_CODES` with the `ErrorCode` union, and the wire types for tool arguments and results, so a TypeScript client can type its calls: ```ts import type { ErrorCode } from 'browserhive'; ``` ## Testing example ```ts import { afterAll, beforeAll, expect, test } from 'bun:test'; import { createServer } from 'browserhive'; const server = await createServer({ port: 0, env: {}, configFile: false, dataDir: './.tmp-browserhive' }); beforeAll(() => server.listen()); afterAll(() => server.stop()); test('health', async () => { const res = await fetch(`${server.url}/health`); expect(res.ok).toBe(true); }); ``` --- # Upgrading and downgrading Source: https://browserhive.ai/docs/guide/upgrading/ BrowserHive follows semantic versioning from `0.1.0`. While the major version is `0`, breaking changes bump the minor version. Release notes are in the changelog and on GitHub Releases. ## Upgrade ```bash bun add -g browserhive@latest # or: npm i -g browserhive@latest / pnpm add -g browserhive@latest browserhive init # refreshes browsers if the Playwright version moved browserhive doctor ``` Then restart the server. On the first start of the new version: 1. The database is checked. If migrations are pending, a backup is written **first** to `/backups/browserhive-v-.db`. 2. Pending migrations run inside one transaction. If any step fails, nothing is applied and the server exits with [`MIGRATION_FAILED`](/docs/reference/errors/#MIGRATION_FAILED); your data is untouched and the backup is there. 3. Only the newest backups are kept (`--backupsKeep`, default 5). See what happened with `browserhive db status`, which shows the schema version, the migration history and the last backup. The dashboard's System page shows the same. To migrate without starting the server, for example in a deployment script: ```bash browserhive db migrate --dryRun # list what would run browserhive db migrate ``` Prereleases are published under the `next` tag: `bun add -g browserhive@next`. ## Downgrade Nothing is ever migrated downward in place. Instead, each database records two numbers: its schema version, and the **oldest schema version whose code can still read it**. Purely additive migrations (new tables or columns) do not raise the second number. - **Within the compatibility window:** an older release opens a newer database normally. - **Outside the window:** the older release refuses to start with [`DB_NEWER_THAN_BINARY`](/docs/reference/errors/#DB_NEWER_THAN_BINARY) (exit code 3). The message names the backup that was written before the upgrade. To go back: ```bash browserhive db status # with the newer version, if still installed bun add -g browserhive@ browserhive db restore ~/.local/share/browserhive/backups/browserhive-v3-20260914T101500Z.db ``` `db restore` refuses while a server is running, checks that the file is a BrowserHive database, and copies the current database aside before replacing it. Anything recorded since the backup (sessions, audit rows, vault bindings changed in the meantime) is not in the restored file. Take a manual backup before risky changes: ```bash browserhive db backup --out ./browserhive-before-change.db ``` ## Browser versions Playwright and Patchright are pinned per release. After an upgrade that moves the pin, the installed browser build does not match the driver, and `launch_session` fails with [`BROWSER_NOT_INSTALLED`](/docs/reference/errors/#BROWSER_NOT_INSTALLED) until you run `browserhive init`. --- # Troubleshooting Source: https://browserhive.ai/docs/guide/troubleshooting/ Start with: ```bash browserhive doctor ``` It prints a ✓ or ✗ per check and the fix for each ✗. Every error code, its cause and resolution is in the [error reference](/docs/reference/errors/). ## Startup **[`BROWSER_NOT_INSTALLED`](/docs/reference/errors/#BROWSER_NOT_INSTALLED)** Run `browserhive init`. If you keep browsers elsewhere, set `PLAYWRIGHT_BROWSERS_PATH` the same way for `init` and the server. With `--stealthDriver patchright`, Patchright's Chromium must be installed too. **[`PORT_IN_USE`](/docs/reference/errors/#PORT_IN_USE)** Another process holds the port, often a BrowserHive you started earlier. Find it with `lsof -i :9876` (macOS, Linux) or `netstat -ano | findstr 9876` (Windows), or use `--port`. **[`INSECURE_BIND_REFUSED`](/docs/reference/errors/#INSECURE_BIND_REFUSED)** You bound a non-loopback host without authentication. Add `--auth token`, or `--allowInsecureBind` on a network you control. See [Security](/docs/guide/security/). **[`ADMIN_REQUIRES_HTTP`](/docs/reference/errors/#ADMIN_REQUIRES_HTTP)** The dashboard is not available under `--transport stdio`. Run the HTTP transport. **[`CONFIG_INVALID`](/docs/reference/errors/#CONFIG_INVALID) or [`CONFIG_UNKNOWN_KEY`](/docs/reference/errors/#CONFIG_UNKNOWN_KEY)** The message names the key, the source it came from (flag, environment variable or file and key) and the accepted values. Common causes: a kebab-case flag, a leftover `BROWSERHIVE_*` variable in your shell, an empty value, or a boolean written as `--admin false` instead of `--admin=false`. `browserhive config validate` reproduces the check, and `browserhive config show` shows which source won. **[`BLOCKLIST_LOAD_FAILED`](/docs/reference/errors/#BLOCKLIST_LOAD_FAILED)** The `--blocklist` file is missing or unreadable. A configured blocklist that cannot be read is fatal on purpose. **[`DB_NEWER_THAN_BINARY`](/docs/reference/errors/#DB_NEWER_THAN_BINARY)** The database was written by a newer BrowserHive. Upgrade again, or restore the pre-upgrade backup: see [Upgrading](/docs/guide/upgrading/#downgrade). **[`MIGRATION_FAILED`](/docs/reference/errors/#MIGRATION_FAILED), [`DB_CORRUPT`](/docs/reference/errors/#DB_CORRUPT)** Nothing was changed; a backup exists in `/backups/`. Run `browserhive db status`, and open an issue with the output. **[`DATA_DIR_UNWRITABLE`](/docs/reference/errors/#DATA_DIR_UNWRITABLE)** Fix ownership of the data directory, or point `--dataDir` somewhere you own. ## Sessions and tools **[`SESSION_LIMIT_REACHED`](/docs/reference/errors/#SESSION_LIMIT_REACHED)** The cap is derived from RAM by default. Close idle sessions, shorten `--sessionLease`, or raise `--maxSessions` if the host can take it. **Sessions disappear** Idle sessions are closed after `--sessionLease` (default `2h`). The dashboard's session list shows the lease countdown and the closed reason. **[`URL_BLOCKED`](/docs/reference/errors/#URL_BLOCKED)** The operator's blocklist matched. The Blocklist page shows which pattern. **[`EVALUATE_DISABLED`](/docs/reference/errors/#EVALUATE_DISABLED)** `--allowEvaluate false` on the server, or `disable_evaluate: true` on the session. **Elements not found or not actionable** Use `snapshot` to see the page as the agent does, `wait_for_selector` before interacting, and check the tool call's screenshot on the session's Timeline tab. **Sites detect automation** Check the session's Identity tab. Make sure `browserhive init` installed Patchright and `--stealth` is not `off`. Read the [stealth ceilings](/docs/guide/stealth/#ceilings); some checks need [human takeover](/docs/guide/attention/). ## Vault **[`VAULT_LOCKED`](/docs/reference/errors/#VAULT_LOCKED)** — paste a session token from `bw unlock --raw` on the dashboard's Vault page, or start the server with `BW_SESSION` exported. **[`VAULT_UNLOCK_FAILED`](/docs/reference/errors/#VAULT_UNLOCK_FAILED)** — the pasted session token was rejected by `bw status`. It expires on `bw lock` or `bw logout`, and only works for the `bw` login of the user running BrowserHive. Run `bw unlock --raw` again and paste the new token. **[`VAULT_BACKEND_ERROR`](/docs/reference/errors/#VAULT_BACKEND_ERROR)** — `bw` is missing, timed out, or failed. Run `bw status` in the same environment as the server. **`vault_fill` returns `blocked` / `not_authorized`** — the entry has no binding for this session slug or principal, or its folder is `reject_all`. Use the origin tester on the Vault page. **`origin_mismatch`** — the page is not on the binding's allowed origins. Remember that `github.com` does not match `gist.github.com`; use `*.github.com`. ## Dashboard **"offline" or back at the login page** — the server restarted or your session expired (15 minutes idle, 8 hours total). Sign in again. **"reconnecting…" for more than a minute** — the server is down or unreachable. **Forgot the password** — stop the server and run `browserhive admin reset-password`. **Live view is black or says "Screencast failed"** — the session is closed or crashed, or the screencast could not start; the panel shows the code. **Takeover input is ignored** — input is only accepted while an attention request is open for that session ([`INPUT_NOT_PERMITTED`](/docs/reference/errors/#INPUT_NOT_PERMITTED)). ## MCP clients **401 on `/mcp`** — `--auth token` is on and the client sent no token or a revoked one. See [MCP clients](/docs/guide/mcp-clients/#authentication-tokens). **stdio client hangs or reports invalid JSON** — something is writing to stdout. BrowserHive never does under stdio; check wrapper scripts. Use absolute paths to `bun` and `browserhive` in desktop apps that do not inherit your shell's `PATH`. **`request_attention` returns `ATTENTION_REQUIRES_HTTP`** — you are on stdio. Use the HTTP transport with `--admin`. ## Logs - `--logLevel debug`, or per module: `--logLevel info,sessions=debug`. You can also change the level at runtime from the System page. - `--logFormat json` for log collectors; JSON logs go to stderr. - The dashboard's Logs page tails the last 5,000 records with filters. --- # FAQ Source: https://browserhive.ai/docs/guide/faq/ **Does it run on Node.js?** No. Bun ≥ 1.4 is the runtime. You can install the package with npm or pnpm, but Bun must be installed to run it. **Why one port for everything?** MCP, the REST API, the WebSocket and the dashboard share `--port`, so there is one bind rule, one authentication surface and one health check (`/health`). **How isolated are sessions?** Each session is its own Chromium process with its own browser context: separate cookies, local storage, IndexedDB, cache and service workers. Nothing is shared between sessions. **Can several agents share a session?** With `--auth token`, a session belongs to the principal (token) that created it and other agents cannot see it. Without authentication every caller is `local` and sees every session. **Do sessions survive a restart?** With `persistence_mode: "persistent"` (or `--persistence persistent`) the profile is kept on disk under the data directory. Memory sessions are gone when they close. Agents can also save a login with `save_storage_state` or `save_full_profile` and restore it in a later session. **Where are my passwords?** In your password manager. BrowserHive stores only bindings (which entry may be filled on which origins, by which sessions) and an audit log without secrets. **Can the model read a password after a fill?** Not through tool results while the redaction window is open, and never from BrowserHive's logs, database or traces. An agent with `evaluate` could read a field that still contains the value later; see [Security](/docs/guide/security/#redaction-and-its-limits) for the mitigations. **Does it phone home?** No. Telemetry is opt-in and goes only to the OTLP endpoint you configure. **Does it solve CAPTCHAs?** No. An agent can ask a human with `request_attention`; see [Human takeover](/docs/guide/attention/). **Is stealth undetectable?** No. It makes the browser coherent and removes common automation signals. The limits are documented in [Stealth](/docs/guide/stealth/#ceilings). **Firefox or WebKit?** Not yet. Chromium, Chrome and Edge channels are supported. **Proxies?** Pass Playwright's `proxy` in `launch_options` (or `context_options`) today. Loopback and private networks are always bypassed. A managed pool is planned. **Can I run it in Docker or on a server?** Yes. Bind with `--host 0.0.0.0 --auth token`, put a TLS reverse proxy in front, and configure it with environment variables; see [Configuration](/docs/guide/configuration/#environment-only-deployments). **How many sessions can I run?** By default `min(floor(RAM in GiB / 1.5), 20)`. Each Chromium session typically needs a few hundred MB. Set `--maxSessions` to override. **How much is recorded, and for how long?** Tool calls, navigations and audit rows are kept for 7 days or up to 1 GiB by default (`--retentionDays`, `--retentionBytes`). `--recordToolResults shape` or `none` stores less. See [Security](/docs/guide/security/#what-is-recorded). **Is it free?** Yes, MIT licensed. --- # Configuration reference Source: https://browserhive.ai/docs/reference/configuration/ Every configuration key of BrowserHive (50 keys), generated from the zod schema in `@browserhive/contracts/config`. For a guided introduction see [the configuration guide](/docs/guide/configuration/). ## Precedence Four sources, lowest to highest precedence. **Rightmost wins.** ``` defaults < environment (BROWSERHIVE_*) < browserhive.config.json < CLI flags ``` Standard `OTEL_*` variables are read as a sub-source just below `BROWSERHIVE_*` (provenance `env(otel)`). When a key is supplied by more than one source, startup logs one line per key naming the winner and what it shadowed; secret values render as ``: ``` config: maxSessions=8 (cli) shadows config-file=4, env=2 config: logLevel=debug (config-file) shadows env=info config: authTokens= (cli) shadows env= ``` The same provenance is shown by `browserhive config show`, the System page of the dashboard and `GET /api/v1/system/config`. An empty value in the environment or the config file is a usage error, not "unset". ## Naming Every key has one camelCase name. The other spellings are mechanical: `--maxSessions`, `BROWSERHIVE_MAX_SESSIONS`, `"maxSessions"`. CLI flags are case-sensitive; kebab-case flags are unknown and fail with a "did you mean" hint. Unknown flags, environment variables and file keys stop startup with exit code 64. | Canonical key | CLI flag | Environment variable | Config file key | |---|---|---|---| | `maxSessions` | `--maxSessions` | `BROWSERHIVE_MAX_SESSIONS` | `"maxSessions"` | | `otelEndpoint` | `--otelEndpoint` | `BROWSERHIVE_OTEL_ENDPOINT` | `"otelEndpoint"` | | `allowInsecureBind` | `--allowInsecureBind` | `BROWSERHIVE_ALLOW_INSECURE_BIND` | `"allowInsecureBind"` | ## Config file discovery First hit wins: `--config ` (or `BROWSERHIVE_CONFIG`), then `./browserhive.config.json`, then `/browserhive.config.json`. The file is plain JSON; `"$schema"` is the only extra key tolerated. A config file found inside the data directory may not set `dataDir`. The JSON Schema is [config.schema.json](/docs/reference/config.schema.json) (also printed by `browserhive config schema`). ## Value grammars | Grammar | Accepted forms | |---|---| | boolean | `true`, `false`, `1`, `0`, `yes`, `no` (case-insensitive). CLI: `--admin` means true, `--admin=false` or `--noAdmin` sets false; `--admin false` is not accepted. | | duration | integer plus `ms`, `s`, `m`, `h` or `d`; a bare integer is milliseconds. | | bytes | integer plus `B`, `KiB`, `MiB`, `GiB`, `KB`, `MB` or `GB`; a bare integer is bytes. | | integer / port | decimal integer, range-checked per key; ports are 1–65535. | | host | IPv4 literal, IPv6 literal (with or without brackets) or an RFC 1123 hostname. | | enum | exact member, case-sensitive. | | path | relative paths resolve against the working directory (CLI, env) or the config file directory (file). | | list | CLI/env: comma-separated, trimmed, no empty items. File: JSON array of strings. | | map | CLI/env: `k=v,k2=v2` (first `=` splits). File: JSON object of strings. | | url | absolute `http:` or `https:` URL. | ## OpenTelemetry environment variables | Variable | Key | |---|---| | `OTEL_EXPORTER_OTLP_ENDPOINT` | [`otelEndpoint`](#otelEndpoint) | | `OTEL_EXPORTER_OTLP_HEADERS` | [`otelHeaders`](#otelHeaders) | | `OTEL_EXPORTER_OTLP_PROTOCOL` | [`otelProtocol`](#otelProtocol) | | `OTEL_SERVICE_NAME` | [`otelServiceName`](#otelServiceName) | | `OTEL_TRACES_SAMPLER_ARG` | [`otelSampleRatio`](#otelSampleRatio) | ## Cross-field rules Checked after all sources are merged. Violations exit with code 64 unless noted. 1. `minAttentionWait` must be less than `attentionTimeout` when it is greater than 0. 2. `humanize=true` requires `stealth` to be `standard` or `max`. 3. `fingerprint=true` (set explicitly) requires `stealth` to be `standard` or `max`. 4. `captcha=attention` (set explicitly) requires `admin=true` and `transport=http`. 5. `admin=true` requires `transport=http` (`ADMIN_REQUIRES_HTTP`, exit 3). 6. `auth=token` requires `transport=http`. 7. `otelEndpoint`, `otelProtocol`, `otelHeaders`, `otelServiceName` and `otelSampleRatio` require `otel=true`. 8. `trustedProxies` requires a non-loopback `host`. 9. `screenshotTrace=true` requires `trace=true`. 10. `blocklistWatch=true` requires `blocklist` to be set. 11. A non-loopback `host` without `auth=token` and without `allowInsecureBind=true` is refused (`INSECURE_BIND_REFUSED`, exit 3). ## Reserved keys These names are reserved for future releases. Setting any of them fails fast with a "reserved" message: `proxy`, `proxies`, `proxyRotation`, `notifications`, `notificationChannels`, `otelMetricsInterval`, `captchaSolver`, `extensions`, `profiles`, `resourceBudget`, `tenant`. ## Server | Key | CLI | Environment | Default | |---|---|---|---| | [`config`](#config) | `--config` | `BROWSERHIVE_CONFIG` | unset | | [`transport`](#transport) | `--transport` | `BROWSERHIVE_TRANSPORT` | `http` | | [`host`](#host) | `--host` | `BROWSERHIVE_HOST` | `127.0.0.1` | | [`port`](#port) | `--port` | `BROWSERHIVE_PORT` | `9876` | | [`auth`](#auth) | `--auth` | `BROWSERHIVE_AUTH` | `off` | | [`authTokens`](#authTokens) | `--authTokens` | `BROWSERHIVE_AUTH_TOKENS` | empty list | | [`allowInsecureBind`](#allowInsecureBind) | `--allowInsecureBind` | `BROWSERHIVE_ALLOW_INSECURE_BIND` | `false` | | [`trustedProxies`](#trustedProxies) | `--trustedProxies` | `BROWSERHIVE_TRUSTED_PROXIES` | empty list | | [`admin`](#admin) | `--admin` | `BROWSERHIVE_ADMIN` | `false` | | [`dataDir`](#dataDir) | `--dataDir` | `BROWSERHIVE_DATA_DIR` | derived (platform) | | [`shutdownTimeout`](#shutdownTimeout) | `--shutdownTimeout` | `BROWSERHIVE_SHUTDOWN_TIMEOUT` | `20s` | | [`sessionCloseTimeout`](#sessionCloseTimeout) | `--sessionCloseTimeout` | `BROWSERHIVE_SESSION_CLOSE_TIMEOUT` | `10s` | ### `config` Config file path. CLI and environment only: a config file cannot point at another. | Property | Value | |---|---| | CLI flag | `--config` | | Environment | `BROWSERHIVE_CONFIG` | | Config file | not accepted | | Type | a non-empty file system path | | Default | unset | | Notes | restart required · CLI and environment only (not accepted in the config file) | ### `transport` Transport to serve. stdio is the single-client fallback without dashboard or attention. | Property | Value | |---|---| | CLI flag | `--transport` | | Environment | `BROWSERHIVE_TRANSPORT` | | Config file | `"transport"` | | Type | one of: http, stdio | | Default | `http` | | Notes | restart required · reserved values `ws` fail fast | ### `host` Bind address. A non-loopback host requires auth=token or allowInsecureBind=true. | Property | Value | |---|---| | CLI flag | `--host` | | Environment | `BROWSERHIVE_HOST` | | Config file | `"host"` | | Type | an IPv4 address, an IPv6 address, or a hostname | | Default | `127.0.0.1` | | Notes | restart required | ### `port` Bind port for MCP, REST, WebSocket and the dashboard. | Property | Value | |---|---| | CLI flag | `--port` | | Environment | `BROWSERHIVE_PORT` | | Config file | `"port"` | | Type | a port between 1 and 65535 | | Default | `9876` | | Notes | restart required | ### `auth` MCP authentication. token requires a bearer on /mcp and enforces session ownership. | Property | Value | |---|---| | CLI flag | `--auth` | | Environment | `BROWSERHIVE_AUTH` | | Config file | `"auth"` | | Type | one of: off, token | | Default | `off` | | Notes | restart required | ### `authTokens` Agent bearer tokens as name:token pairs (env preferred). Merged with stored tokens, never persisted. | Property | Value | |---|---| | CLI flag | `--authTokens` | | Environment | `BROWSERHIVE_AUTH_TOKENS` | | Config file | `"authTokens"` | | Type | a comma-separated list of 'name:token' pairs (token >= 32 characters) | | Default | empty list | | Examples | `ci-runner:REPLACE_WITH_32_PLUS_CHARS` | | Notes | restart required · secret (rendered ``) | ### `allowInsecureBind` Acknowledge binding a non-loopback host without authentication. | Property | Value | |---|---| | CLI flag | `--allowInsecureBind` | | Environment | `BROWSERHIVE_ALLOW_INSECURE_BIND` | | Config file | `"allowInsecureBind"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `trustedProxies` Peers whose X-Forwarded-For is honoured (IPs or CIDR ranges). Never used on a loopback bind. | Property | Value | |---|---| | CLI flag | `--trustedProxies` | | Environment | `BROWSERHIVE_TRUSTED_PROXIES` | | Config file | `"trustedProxies"` | | Type | a comma-separated list of IP addresses or CIDR ranges | | Default | empty list | | Notes | restart required | ### `admin` Enable the dashboard, REST API, WebSocket and trace viewer (http only). | Property | Value | |---|---| | CLI flag | `--admin` | | Environment | `BROWSERHIVE_ADMIN` | | Config file | `"admin"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `dataDir` Data directory (database, sessions, auth states, uploads, backups). Defaults to the OS data dir. | Property | Value | |---|---| | CLI flag | `--dataDir` | | Environment | `BROWSERHIVE_DATA_DIR` | | Config file | `"dataDir"` | | Type | a non-empty file system path | | Default | derived from the OS: `~/Library/Application Support/BrowserHive` (macOS), `%LOCALAPPDATA%\BrowserHive` (Windows), `$XDG_DATA_HOME/browserhive` or `~/.local/share/browserhive` (Linux) | | Notes | restart required | ### `shutdownTimeout` Total budget for a graceful stop (listeners, then sessions, then storage). | Property | Value | |---|---| | CLI flag | `--shutdownTimeout` | | Environment | `BROWSERHIVE_SHUTDOWN_TIMEOUT` | | Config file | `"shutdownTimeout"` | | Type | a duration like '2h', '30m', '90s', '500ms', or an integer of milliseconds | | Default | `20s` | | Notes | restart required | ### `sessionCloseTimeout` Per-session close and trace-finalize cap. | Property | Value | |---|---| | CLI flag | `--sessionCloseTimeout` | | Environment | `BROWSERHIVE_SESSION_CLOSE_TIMEOUT` | | Config file | `"sessionCloseTimeout"` | | Type | a duration like '2h', '30m', '90s', '500ms', or an integer of milliseconds | | Default | `10s` | | Notes | restart required | ## Sessions | Key | CLI | Environment | Default | |---|---|---|---| | [`persistence`](#persistence) | `--persistence` | `BROWSERHIVE_PERSISTENCE` | `memory` | | [`defaultHeadless`](#defaultHeadless) | `--defaultHeadless` | `BROWSERHIVE_DEFAULT_HEADLESS` | `true` | | [`defaultChannel`](#defaultChannel) | `--defaultChannel` | `BROWSERHIVE_DEFAULT_CHANNEL` | `chromium` | | [`maxSessions`](#maxSessions) | `--maxSessions` | `BROWSERHIVE_MAX_SESSIONS` | derived (hostMemory) | | [`sessionLease`](#sessionLease) | `--sessionLease` | `BROWSERHIVE_SESSION_LEASE` | `2h` | | [`attentionTimeout`](#attentionTimeout) | `--attentionTimeout` | `BROWSERHIVE_ATTENTION_TIMEOUT` | `6h` | | [`minAttentionWait`](#minAttentionWait) | `--minAttentionWait` | `BROWSERHIVE_MIN_ATTENTION_WAIT` | `30m` | | [`allowEvaluate`](#allowEvaluate) | `--allowEvaluate` | `BROWSERHIVE_ALLOW_EVALUATE` | `true` | | [`blocklist`](#blocklist) | `--blocklist` | `BROWSERHIVE_BLOCKLIST` | unset | | [`blocklistWatch`](#blocklistWatch) | `--blocklistWatch` | `BROWSERHIVE_BLOCKLIST_WATCH` | `false` | | [`vault`](#vault) | `--vault` | `BROWSERHIVE_VAULT` | `off` | ### `persistence` Default persistence mode; launch_session persistence_mode overrides per session. | Property | Value | |---|---| | CLI flag | `--persistence` | | Environment | `BROWSERHIVE_PERSISTENCE` | | Config file | `"persistence"` | | Type | one of: memory, persistent, storage-state | | Default | `memory` | | Notes | restart required · reserved values `blueprint` fail fast | ### `defaultHeadless` Default headless mode; launch_session headless overrides per session. | Property | Value | |---|---| | CLI flag | `--defaultHeadless` | | Environment | `BROWSERHIVE_DEFAULT_HEADLESS` | | Config file | `"defaultHeadless"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `true` | | Notes | restart required | ### `defaultChannel` Default browser channel; launch_session channel overrides per session. | Property | Value | |---|---| | CLI flag | `--defaultChannel` | | Environment | `BROWSERHIVE_DEFAULT_CHANNEL` | | Config file | `"defaultChannel"` | | Type | one of: chromium, chrome, edge | | Default | `chromium` | | Notes | restart required | ### `maxSessions` Maximum concurrent browser sessions, or unbounded. Derived from host RAM when unset (min(floor(GiB / 1.5), 20)). | Property | Value | |---|---| | CLI flag | `--maxSessions` | | Environment | `BROWSERHIVE_MAX_SESSIONS` | | Config file | `"maxSessions"` | | Type | an integer >= 1 or 'unbounded' | | Default | derived from host RAM: `min(floor(RAM_GiB / 1.5), 20)` | | Notes | restart required | ### `sessionLease` Sliding inactivity lease after which an idle session is reaped. | Property | Value | |---|---| | CLI flag | `--sessionLease` | | Environment | `BROWSERHIVE_SESSION_LEASE` | | Config file | `"sessionLease"` | | Type | a duration of at least '1m' | | Default | `2h` | | Notes | restart required | ### `attentionTimeout` Server cap on how long request_attention may block. | Property | Value | |---|---| | CLI flag | `--attentionTimeout` | | Environment | `BROWSERHIVE_ATTENTION_TIMEOUT` | | Config file | `"attentionTimeout"` | | Type | a duration of at least '1m' | | Default | `6h` | | Notes | restart required | ### `minAttentionWait` Floor for request_attention max_wait; 0 disables it. Must be less than attentionTimeout. | Property | Value | |---|---| | CLI flag | `--minAttentionWait` | | Environment | `BROWSERHIVE_MIN_ATTENTION_WAIT` | | Config file | `"minAttentionWait"` | | Type | a duration like '2h', '30m', '90s', '500ms', or an integer of milliseconds | | Default | `30m` | | Notes | restart required | ### `allowEvaluate` Allow the evaluate tool. false makes every evaluate return EVALUATE_DISABLED. | Property | Value | |---|---| | CLI flag | `--allowEvaluate` | | Environment | `BROWSERHIVE_ALLOW_EVALUATE` | | Config file | `"allowEvaluate"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `true` | | Notes | restart required | ### `blocklist` URL blocklist file (one glob per line). Unreadable at startup is fatal. | Property | Value | |---|---| | CLI flag | `--blocklist` | | Environment | `BROWSERHIVE_BLOCKLIST` | | Config file | `"blocklist"` | | Type | a non-empty file system path | | Default | unset | | Notes | restart required | ### `blocklistWatch` Reload the blocklist when the file changes (debounced). Requires blocklist. | Property | Value | |---|---| | CLI flag | `--blocklistWatch` | | Environment | `BROWSERHIVE_BLOCKLIST_WATCH` | | Config file | `"blocklistWatch"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `vault` Credential vault backend. | Property | Value | |---|---| | CLI flag | `--vault` | | Environment | `BROWSERHIVE_VAULT` | | Config file | `"vault"` | | Type | one of: off, bitwarden | | Default | `off` | | Notes | restart required · reserved values `local`, `onepassword`, `http` fail fast | ## Stealth | Key | CLI | Environment | Default | |---|---|---|---| | [`stealth`](#stealth) | `--stealth` | `BROWSERHIVE_STEALTH` | `standard` | | [`stealthDriver`](#stealthDriver) | `--stealthDriver` | `BROWSERHIVE_STEALTH_DRIVER` | `auto` | | [`fingerprint`](#fingerprint) | `--fingerprint` | `BROWSERHIVE_FINGERPRINT` | derived (stealth) | | [`humanize`](#humanize) | `--humanize` | `BROWSERHIVE_HUMANIZE` | `false` | | [`captcha`](#captcha) | `--captcha` | `BROWSERHIVE_CAPTCHA` | `attention` | ### `stealth` Stealth level. max also defaults fingerprint to true. | Property | Value | |---|---| | CLI flag | `--stealth` | | Environment | `BROWSERHIVE_STEALTH` | | Config file | `"stealth"` | | Type | one of: off, standard, max | | Default | `standard` | | Notes | restart required | ### `stealthDriver` Chromium driver for stealth sessions. auto uses Patchright when installed, else Playwright. | Property | Value | |---|---| | CLI flag | `--stealthDriver` | | Environment | `BROWSERHIVE_STEALTH_DRIVER` | | Config file | `"stealthDriver"` | | Type | one of: auto, patchright, playwright | | Default | `auto` | | Notes | restart required | ### `fingerprint` Coherent fingerprint identity per session. Defaults to true only when stealth=max. | Property | Value | |---|---| | CLI flag | `--fingerprint` | | Environment | `BROWSERHIVE_FINGERPRINT` | | Config file | `"fingerprint"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | derived from `stealth`: `true` when `stealth=max`, otherwise `false` | | Notes | restart required | ### `humanize` Human-like cursor movement and typing cadence. Requires stealth standard or max. | Property | Value | |---|---| | CLI flag | `--humanize` | | Environment | `BROWSERHIVE_HUMANIZE` | | Config file | `"humanize"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `captcha` CAPTCHA policy. attention hands the page to an operator (needs admin and http). | Property | Value | |---|---| | CLI flag | `--captcha` | | Environment | `BROWSERHIVE_CAPTCHA` | | Config file | `"captcha"` | | Type | one of: attention, off | | Default | `attention` | | Notes | restart required · reserved values `solver` fail fast | ## Logging | Key | CLI | Environment | Default | |---|---|---|---| | [`logLevel`](#logLevel) | `--logLevel` | `BROWSERHIVE_LOG_LEVEL` | `info` | | [`logFormat`](#logFormat) | `--logFormat` | `BROWSERHIVE_LOG_FORMAT` | `auto` | | [`color`](#color) | `--color` | `BROWSERHIVE_COLOR` | `auto` | | [`logRingSize`](#logRingSize) | `--logRingSize` | `BROWSERHIVE_LOG_RING_SIZE` | `5000` | | [`logPersist`](#logPersist) | `--logPersist` | `BROWSERHIVE_LOG_PERSIST` | `off` | ### `logLevel` Log level, optionally per module: info,sessions=debug,http=warn. Levels: error, warn, info, debug, trace. | Property | Value | |---|---| | CLI flag | `--logLevel` | | Environment | `BROWSERHIVE_LOG_LEVEL` | | Config file | `"logLevel"` | | Type | a level spec like 'info' or 'info,sessions=debug,http=warn' (levels: error, warn, info, debug, trace) | | Default | `info` | | Examples | `info`, `info,sessions=debug` | | Notes | runtime-adjustable | ### `logFormat` Log renderer. auto is pretty on a TTY (http only), otherwise JSON lines. | Property | Value | |---|---| | CLI flag | `--logFormat` | | Environment | `BROWSERHIVE_LOG_FORMAT` | | Config file | `"logFormat"` | | Type | one of: auto, json, pretty | | Default | `auto` | | Notes | runtime-adjustable | ### `color` Colour for logs and CLI output. auto honours NO_COLOR, FORCE_COLOR, TERM=dumb and TTY. | Property | Value | |---|---| | CLI flag | `--color` | | Environment | `BROWSERHIVE_COLOR` | | Config file | `"color"` | | Type | one of: auto, always, never | | Default | `auto` | | Notes | restart required | ### `logRingSize` Records kept in the in-process log ring buffer served to the dashboard. | Property | Value | |---|---| | CLI flag | `--logRingSize` | | Environment | `BROWSERHIVE_LOG_RING_SIZE` | | Config file | `"logRingSize"` | | Type | an integer between 100 and 1000000 | | Default | `5000` | | Notes | restart required | ### `logPersist` Durable logs table threshold (retention 3 days). off disables the sink. | Property | Value | |---|---| | CLI flag | `--logPersist` | | Environment | `BROWSERHIVE_LOG_PERSIST` | | Config file | `"logPersist"` | | Type | one of: info, warn, off | | Default | `off` | | Notes | restart required | ## Recording and retention | Key | CLI | Environment | Default | |---|---|---|---| | [`trace`](#trace) | `--trace` | `BROWSERHIVE_TRACE` | derived (admin) | | [`screenshotTrace`](#screenshotTrace) | `--screenshotTrace` | `BROWSERHIVE_SCREENSHOT_TRACE` | `false` | | [`screencastQuality`](#screencastQuality) | `--screencastQuality` | `BROWSERHIVE_SCREENCAST_QUALITY` | `60` | | [`recordToolResults`](#recordToolResults) | `--recordToolResults` | `BROWSERHIVE_RECORD_TOOL_RESULTS` | `full` | | [`retentionDays`](#retentionDays) | `--retentionDays` | `BROWSERHIVE_RETENTION_DAYS` | `7` | | [`retentionBytes`](#retentionBytes) | `--retentionBytes` | `BROWSERHIVE_RETENTION_BYTES` | `1GiB` | | [`backupsKeep`](#backupsKeep) | `--backupsKeep` | `BROWSERHIVE_BACKUPS_KEEP` | `5` | | [`urlQueryAllowlist`](#urlQueryAllowlist) | `--urlQueryAllowlist` | `BROWSERHIVE_URL_QUERY_ALLOWLIST` | empty list | ### `trace` Record a Playwright trace per session. Defaults to the value of admin. | Property | Value | |---|---| | CLI flag | `--trace` | | Environment | `BROWSERHIVE_TRACE` | | Config file | `"trace"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | derived from `admin`: same value | | Notes | restart required | ### `screenshotTrace` Store a JPEG after each tool call. Requires trace=true. | Property | Value | |---|---| | CLI flag | `--screenshotTrace` | | Environment | `BROWSERHIVE_SCREENSHOT_TRACE` | | Config file | `"screenshotTrace"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `screencastQuality` JPEG quality of the live view screencast (1-100). | Property | Value | |---|---| | CLI flag | `--screencastQuality` | | Environment | `BROWSERHIVE_SCREENCAST_QUALITY` | | Config file | `"screencastQuality"` | | Type | an integer between 1 and 100 | | Default | `60` | | Notes | restart required | ### `recordToolResults` What of a tool result is persisted: full text (capped), shape (keys and sizes) or none. | Property | Value | |---|---| | CLI flag | `--recordToolResults` | | Environment | `BROWSERHIVE_RECORD_TOOL_RESULTS` | | Config file | `"recordToolResults"` | | Type | one of: full, shape, none | | Default | `full` | | Notes | restart required | ### `retentionDays` Days to keep events and artifacts. Must be at least 1; use a large number to keep longer. | Property | Value | |---|---| | CLI flag | `--retentionDays` | | Environment | `BROWSERHIVE_RETENTION_DAYS` | | Config file | `"retentionDays"` | | Type | an integer >= 1 | | Default | `7` | | Notes | restart required | ### `retentionBytes` Disk budget for the database and artifacts before the oldest rows are pruned. | Property | Value | |---|---| | CLI flag | `--retentionBytes` | | Environment | `BROWSERHIVE_RETENTION_BYTES` | | Config file | `"retentionBytes"` | | Type | a size of at least '64MiB' | | Default | `1GiB` | | Notes | restart required | ### `backupsKeep` Pre-migration database backups to retain. | Property | Value | |---|---| | CLI flag | `--backupsKeep` | | Environment | `BROWSERHIVE_BACKUPS_KEEP` | | Config file | `"backupsKeep"` | | Type | an integer >= 1 | | Default | `5` | | Notes | restart required | ### `urlQueryAllowlist` Query parameter names kept when URLs are sanitized for storage; all others are stripped. | Property | Value | |---|---| | CLI flag | `--urlQueryAllowlist` | | Environment | `BROWSERHIVE_URL_QUERY_ALLOWLIST` | | Config file | `"urlQueryAllowlist"` | | Type | a comma-separated list like 'a,b,c' (or a JSON array of strings) | | Default | empty list | | Notes | restart required | ## Telemetry | Key | CLI | Environment | Default | |---|---|---|---| | [`otel`](#otel) | `--otel` | `BROWSERHIVE_OTEL` | `false` | | [`otelEndpoint`](#otelEndpoint) | `--otelEndpoint` | `BROWSERHIVE_OTEL_ENDPOINT` | `http://127.0.0.1:4318` | | [`otelProtocol`](#otelProtocol) | `--otelProtocol` | `BROWSERHIVE_OTEL_PROTOCOL` | `http/protobuf` | | [`otelHeaders`](#otelHeaders) | `--otelHeaders` | `BROWSERHIVE_OTEL_HEADERS` | `{}` | | [`otelServiceName`](#otelServiceName) | `--otelServiceName` | `BROWSERHIVE_OTEL_SERVICE_NAME` | `browserhive` | | [`otelSampleRatio`](#otelSampleRatio) | `--otelSampleRatio` | `BROWSERHIVE_OTEL_SAMPLE_RATIO` | `1` | | [`otelSignals`](#otelSignals) | `--otelSignals` | `BROWSERHIVE_OTEL_SIGNALS` | `["traces","metrics","logs"]` | | [`otelVerbose`](#otelVerbose) | `--otelVerbose` | `BROWSERHIVE_OTEL_VERBOSE` | `false` | | [`otelTraceUrlTemplate`](#otelTraceUrlTemplate) | `--otelTraceUrlTemplate` | `BROWSERHIVE_OTEL_TRACE_URL_TEMPLATE` | unset | ### `otel` Export traces, metrics and logs over OTLP/HTTP. | Property | Value | |---|---| | CLI flag | `--otel` | | Environment | `BROWSERHIVE_OTEL` | | Config file | `"otel"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `otelEndpoint` OTLP/HTTP base URL; /v1/traces, /v1/metrics and /v1/logs are appended. | Property | Value | |---|---| | CLI flag | `--otelEndpoint` | | Environment | `BROWSERHIVE_OTEL_ENDPOINT` | | Config file | `"otelEndpoint"` | | Type | an absolute http: or https: URL like 'http://127.0.0.1:4318' | | Default | `http://127.0.0.1:4318` | | Notes | restart required | ### `otelProtocol` OTLP/HTTP encoding. | Property | Value | |---|---| | CLI flag | `--otelProtocol` | | Environment | `BROWSERHIVE_OTEL_PROTOCOL` | | Config file | `"otelProtocol"` | | Type | one of: http/protobuf, http/json | | Default | `http/protobuf` | | Notes | restart required | ### `otelHeaders` Headers sent with every OTLP request, e.g. Authorization=Bearer … | Property | Value | |---|---| | CLI flag | `--otelHeaders` | | Environment | `BROWSERHIVE_OTEL_HEADERS` | | Config file | `"otelHeaders"` | | Type | a comma-separated map like 'k=v,k2=v2' (or a JSON object of strings) | | Default | `{}` | | Notes | restart required · secret (rendered ``) | ### `otelServiceName` service.name resource attribute. | Property | Value | |---|---| | CLI flag | `--otelServiceName` | | Environment | `BROWSERHIVE_OTEL_SERVICE_NAME` | | Config file | `"otelServiceName"` | | Type | a non-empty string | | Default | `browserhive` | | Notes | restart required | ### `otelSampleRatio` Parent-based ratio sampler for traces (0-1). | Property | Value | |---|---| | CLI flag | `--otelSampleRatio` | | Environment | `BROWSERHIVE_OTEL_SAMPLE_RATIO` | | Config file | `"otelSampleRatio"` | | Type | a number between 0 and 1 | | Default | `1` | | Notes | restart required | ### `otelSignals` Signals to export. | Property | Value | |---|---| | CLI flag | `--otelSignals` | | Environment | `BROWSERHIVE_OTEL_SIGNALS` | | Config file | `"otelSignals"` | | Type | a comma-separated subset of: traces, metrics, logs | | Default | `["traces","metrics","logs"]` | | Notes | restart required | ### `otelVerbose` Also export db.query and cdp.command spans. | Property | Value | |---|---| | CLI flag | `--otelVerbose` | | Environment | `BROWSERHIVE_OTEL_VERBOSE` | | Config file | `"otelVerbose"` | | Type | a boolean: 'true', 'false', '1', '0', 'yes' or 'no' | | Default | `false` | | Notes | restart required | ### `otelTraceUrlTemplate` Dashboard deep-link template for a trace; {trace_id} is substituted. Used only by the dashboard. | Property | Value | |---|---| | CLI flag | `--otelTraceUrlTemplate` | | Environment | `BROWSERHIVE_OTEL_TRACE_URL_TEMPLATE` | | Config file | `"otelTraceUrlTemplate"` | | Type | a non-empty string | | Default | unset | | Examples | `https://grafana.local/explore?traceId={trace_id}` | | Notes | runtime-adjustable | --- # MCP tool reference Source: https://browserhive.ai/docs/reference/tools/ The 43 tools BrowserHive registers, in registration order, generated from `TOOL_CONTRACTS` in `@browserhive/contracts/tools`. Names, parameters, defaults and result shapes are a frozen contract. Every page-targeting tool accepts an optional `tab_id` (defaults to the active tab). Errors are returned as `[CODE] message` text; see the [error reference](/docs/reference/errors/). Defaults shown for `launch_session` `channel` and `headless` are the contract defaults; a server started with `--defaultChannel` / `--defaultHeadless` advertises its configured values in `tools/list`. ## Catalog | # | Tool | Pack | Title | RO | Destructive | Idempotent | Open world | |---|---|---|---|---|---|---|---| | 1 | [`launch_session`](#launch_session) | Lifecycle | Launch session | no | no | no | yes | | 2 | [`close_session`](#close_session) | Lifecycle | Close session | no | yes | yes | no | | 3 | [`list_sessions`](#list_sessions) | Lifecycle | List sessions | yes | no | yes | no | | 4 | [`server_status`](#server_status) | Introspection | Server status | yes | no | yes | no | | 5 | [`session_info`](#session_info) | Introspection | Session info | yes | no | yes | no | | 6 | [`navigate`](#navigate) | Navigation | Navigate | no | no | no | yes | | 7 | [`go_back`](#go_back) | Navigation | Go back | no | no | no | yes | | 8 | [`go_forward`](#go_forward) | Navigation | Go forward | no | no | no | yes | | 9 | [`reload`](#reload) | Navigation | Reload | no | no | yes | yes | | 10 | [`wait_for_url`](#wait_for_url) | Navigation | Wait for URL | yes | no | yes | no | | 11 | [`new_tab`](#new_tab) | Tabs | New tab | no | no | no | yes | | 12 | [`close_tab`](#close_tab) | Tabs | Close tab | no | yes | yes | no | | 13 | [`switch_tab`](#switch_tab) | Tabs | Switch tab | no | no | yes | no | | 14 | [`list_tabs`](#list_tabs) | Tabs | List tabs | yes | no | yes | no | | 15 | [`click`](#click) | Interaction | Click | no | no | no | yes | | 16 | [`type_text`](#type_text) | Interaction | Type text | no | no | no | yes | | 17 | [`fill`](#fill) | Interaction | Fill | no | no | yes | yes | | 18 | [`press_key`](#press_key) | Interaction | Press key | no | no | no | yes | | 19 | [`hover`](#hover) | Interaction | Hover | no | no | yes | yes | | 20 | [`select_option`](#select_option) | Interaction | Select option | no | no | yes | yes | | 21 | [`scroll`](#scroll) | Interaction | Scroll | no | no | no | yes | | 22 | [`drag_and_drop`](#drag_and_drop) | Interaction | Drag and drop | no | no | no | yes | | 23 | [`screenshot`](#screenshot) | Inspection | Screenshot | yes | no | yes | no | | 24 | [`snapshot`](#snapshot) | Inspection | Snapshot | yes | no | yes | no | | 25 | [`get_content`](#get_content) | Inspection | Get content | yes | no | yes | no | | 26 | [`evaluate`](#evaluate) | Inspection | Evaluate | no | no | no | yes | | 27 | [`wait_for_selector`](#wait_for_selector) | Waits | Wait for selector | yes | no | yes | no | | 28 | [`wait_for_load_state`](#wait_for_load_state) | Waits | Wait for load state | yes | no | yes | no | | 29 | [`accept_next_dialog`](#accept_next_dialog) | Dialogs | Accept next dialog | no | no | yes | no | | 30 | [`dismiss_next_dialog`](#dismiss_next_dialog) | Dialogs | Dismiss next dialog | no | no | yes | no | | 31 | [`get_cookies`](#get_cookies) | Cookies and state | Get cookies | yes | no | yes | no | | 32 | [`set_cookies`](#set_cookies) | Cookies and state | Set cookies | no | no | yes | no | | 33 | [`set_viewport`](#set_viewport) | Cookies and state | Set viewport | no | no | yes | no | | 34 | [`set_extra_http_headers`](#set_extra_http_headers) | Cookies and state | Set extra HTTP headers | no | no | yes | no | | 35 | [`upload_file`](#upload_file) | Files | Upload file | no | no | yes | no | | 36 | [`download_file`](#download_file) | Files | Download file | no | no | no | yes | | 37 | [`save_storage_state`](#save_storage_state) | Auth states | Save storage state | no | no | yes | no | | 38 | [`save_full_profile`](#save_full_profile) | Auth states | Save full profile | no | no | yes | no | | 39 | [`list_saved_auths`](#list_saved_auths) | Auth states | List saved auths | yes | no | yes | no | | 40 | [`request_attention`](#request_attention) | Attention (HTTP transport only) | Request attention | no | no | no | no | | 41 | [`get_attention_result`](#get_attention_result) | Attention (HTTP transport only) | Get attention result | yes | no | yes | no | | 42 | [`vault_list_available`](#vault_list_available) | Vault | Vault: list available | yes | no | yes | no | | 43 | [`vault_fill`](#vault_fill) | Vault | Vault: fill | no | no | no | yes | ## Lifecycle ### `launch_session` **Launch session** · capability `lifecycle` · since 0.1.0 > Launch a new isolated browser session. Each session owns its own Playwright driver, browser, context, and page so cookies and storage never leak between sessions. The resolved session_id is '-' and is returned as the 'session_id' field. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `slug` | `string` | yes | — | pattern `^[a-z][a-z0-9-]{1,31}$` | | `channel` | one of `chromium`, `chrome`, `edge` | no | `"chromium"` | — | | `incognito` | `boolean` | no | `false` | — | | `headless` | `boolean` | no | `true` | — | | `persistence_mode` | one of `memory`, `persistent`, `storage-state` | no | — | — | | `restore_profile` | `string` | no | — | — | | `launch_options` | `object` | no | — | keys `args`, `executablePath`; additional keys allowed | | `context_options` | `object` | no | — | additional keys allowed | | `disable_evaluate` | `boolean` | no | `false` | — | | `vault_enabled` | `boolean` | no | `true` | — | | `stealth` | `boolean` | no | — | — | | `fingerprint` | `boolean` | no | — | — | | `humanize` | `boolean` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `slug` | `string` | yes | | `channel` | one of `chromium`, `chrome`, `edge` | yes | | `incognito` | `boolean` | yes | | `headless` | `boolean` | yes | | `persistence_mode` | one of `memory`, `persistent`, `storage-state` | yes | | `current_url` | `string` or `null` | yes | | `created_at` | `number` | yes | | `owner` | `string` | yes | | `lease_expires_at` | `number` | yes | | `lease_paused_at` | `number` or `null` | yes | | `disable_evaluate` | `boolean` | yes | | `vault_enabled` | `boolean` | yes | | `stealth` | `boolean` | yes | | `fingerprint` | `boolean` | yes | | `humanize` | `boolean` | yes | | `identity` | `object` or `null` | yes | | `proxy_label` | `string` or `null` | yes | Errors: [`INVALID_SLUG`](/docs/reference/errors/#INVALID_SLUG), [`UNKNOWN_CHANNEL`](/docs/reference/errors/#UNKNOWN_CHANNEL), [`SESSION_LIMIT_REACHED`](/docs/reference/errors/#SESSION_LIMIT_REACHED), [`SESSION_ALREADY_EXISTS`](/docs/reference/errors/#SESSION_ALREADY_EXISTS), [`UNSAFE_LAUNCH_ARG`](/docs/reference/errors/#UNSAFE_LAUNCH_ARG), [`INVALID_PERSISTENCE_CONFIG`](/docs/reference/errors/#INVALID_PERSISTENCE_CONFIG), [`AUTH_STATE_NOT_FOUND`](/docs/reference/errors/#AUTH_STATE_NOT_FOUND), [`BROWSER_NOT_INSTALLED`](/docs/reference/errors/#BROWSER_NOT_INSTALLED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `close_session` **Close session** · capability `lifecycle` · since 0.1.0 > Close an existing browser session and release all its resources. Annotations: readOnly no · destructive yes · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `closed` | `boolean` | yes | Errors: none documented. Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `list_sessions` **List sessions** · capability `lifecycle` · since 0.1.0 > Return metadata for every live session. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: none. Result: a JSON array; each item has: | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `slug` | `string` | yes | | `channel` | one of `chromium`, `chrome`, `edge` | yes | | `incognito` | `boolean` | yes | | `headless` | `boolean` | yes | | `persistence_mode` | one of `memory`, `persistent`, `storage-state` | yes | | `current_url` | `string` or `null` | yes | | `created_at` | `number` | yes | | `owner` | `string` | yes | | `lease_expires_at` | `number` | yes | | `lease_paused_at` | `number` or `null` | yes | | `disable_evaluate` | `boolean` | yes | | `vault_enabled` | `boolean` | yes | | `stealth` | `boolean` | yes | | `fingerprint` | `boolean` | yes | | `humanize` | `boolean` | yes | | `identity` | `object` or `null` | yes | | `proxy_label` | `string` or `null` | yes | Errors: none documented. Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Introspection ### `server_status` **Server status** · capability `read` · since 0.1.0 > Report server-wide status: uptime, version, transport, live/allowed session counts, vault state, and the global default persistence mode. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: none. Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `uptime_ms` | `number` | yes | | `version` | `string` | yes | | `transport` | one of `stdio`, `http` | yes | | `sessions` | `object` | yes | | `vault` | `object` | yes | | `persistence_mode` | one of `memory`, `persistent`, `storage-state` | yes | Errors: none documented. Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `session_info` **Session info** · capability `read` · since 0.1.0 > Report a single session's configuration and live state: channel, headless/incognito, persistence mode, per-session evaluate/vault flags, open tab (page) count, current URL, created_at, last_tool_at, and navigation count. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `config` | `object` | yes | | `page_count` | `number` | yes | | `current_url` | `string` or `null` | yes | | `created_at` | `number` | yes | | `last_tool_at` | `number` | yes | | `lease_expires_at` | `number` | yes | | `navigation_count` | `number` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Navigation ### `navigate` **Navigate** · capability `navigate` · since 0.1.0 > Navigate a tab to a URL (defaults to the active tab). The operator may maintain a URL blocklist; a blocked target fails with URL_BLOCKED and must not be retried. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `url` | `string` | yes | — | — | | `wait_until` | one of `load`, `domcontentloaded`, `networkidle`, `commit` | no | `"load"` | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `url` | `string` | yes | | `status` | `number` or `null` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`URL_BLOCKED`](/docs/reference/errors/#URL_BLOCKED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`NAVIGATION_TIMEOUT`](/docs/reference/errors/#NAVIGATION_TIMEOUT), [`NAVIGATION_FAILED`](/docs/reference/errors/#NAVIGATION_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `go_back` **Go back** · capability `navigate` · since 0.1.0 > Navigate back in a tab's history (defaults to the active tab). Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `wait_until` | one of `load`, `domcontentloaded`, `networkidle`, `commit` | no | `"load"` | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`NAVIGATION_TIMEOUT`](/docs/reference/errors/#NAVIGATION_TIMEOUT), [`NAVIGATION_FAILED`](/docs/reference/errors/#NAVIGATION_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `go_forward` **Go forward** · capability `navigate` · since 0.1.0 > Navigate forward in a tab's history (defaults to the active tab). Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `wait_until` | one of `load`, `domcontentloaded`, `networkidle`, `commit` | no | `"load"` | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`NAVIGATION_TIMEOUT`](/docs/reference/errors/#NAVIGATION_TIMEOUT), [`NAVIGATION_FAILED`](/docs/reference/errors/#NAVIGATION_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `reload` **Reload** · capability `navigate` · since 0.1.0 > Reload the current page in a tab (defaults to the active tab). Annotations: readOnly no · destructive no · idempotent yes · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `wait_until` | one of `load`, `domcontentloaded`, `networkidle`, `commit` | no | `"load"` | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`NAVIGATION_TIMEOUT`](/docs/reference/errors/#NAVIGATION_TIMEOUT), [`NAVIGATION_FAILED`](/docs/reference/errors/#NAVIGATION_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `wait_for_url` **Wait for URL** · capability `read` · since 0.1.0 > Wait until a tab's URL matches. `url` may be a string (exact/glob) or a { pattern, flags? } object compiled to a regular expression. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `url` | `string` or `object` | yes | — | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`WAIT_TIMEOUT`](/docs/reference/errors/#WAIT_TIMEOUT). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Tabs ### `new_tab` **New tab** · capability `navigate` · since 0.1.0 > Open a new tab in the session and make it active. Optionally navigate it to a URL. Returns the stable tab_id other tools accept via their optional tab_id argument. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `url` | `string` | no | — | — | | `wait_until` | one of `load`, `domcontentloaded`, `networkidle`, `commit` | no | `"load"` | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `tab_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`URL_BLOCKED`](/docs/reference/errors/#URL_BLOCKED), [`NAVIGATION_TIMEOUT`](/docs/reference/errors/#NAVIGATION_TIMEOUT), [`NAVIGATION_FAILED`](/docs/reference/errors/#NAVIGATION_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `close_tab` **Close tab** · capability `mutate` · since 0.1.0 > Close a tab by id. If it was the active tab, another open tab becomes active. Annotations: readOnly no · destructive yes · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `tab_id` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `tab_id` | `string` | yes | | `closed` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `switch_tab` **Switch tab** · capability `mutate` · since 0.1.0 > Make the given tab the active tab for subsequent tab_id-less tool calls. Annotations: readOnly no · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `tab_id` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `tab_id` | `string` | yes | | `url` | `string` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `list_tabs` **List tabs** · capability `read` · since 0.1.0 > List every open tab: its tab_id, current URL, title, and whether it is active. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | Result: a JSON array; each item has: | Field | Type | Always present | |---|---|---| | `tab_id` | `string` | yes | | `url` | `string` | yes | | `title` | `string` | yes | | `active` | `boolean` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Interaction ### `click` **Click** · capability `mutate` · since 0.1.0 > Click an element matching the selector. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `selector` | `string` | yes | — | min length 1 | | `button` | one of `left`, `right`, `middle` | no | `"left"` | — | | `click_count` | `integer` | no | `1` | ≥ 1; ≤ 3 | | `modifiers` | array of one of `Alt`, `Control`, `ControlOrMeta`, `Meta`, `Shift` | no | — | — | | `position` | `object` | no | — | keys `x`, `y` | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `selector` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`ELEMENT_NOT_ACTIONABLE`](/docs/reference/errors/#ELEMENT_NOT_ACTIONABLE). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `type_text` **Type text** · capability `mutate` · since 0.1.0 > Type text into the element one character at a time (simulates typing). Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `selector` | `string` | yes | — | min length 1 | | `text` | `string` | yes | — | — | | `delay` | `number` | no | `0` | ≥ 0 | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `selector` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`ELEMENT_NOT_ACTIONABLE`](/docs/reference/errors/#ELEMENT_NOT_ACTIONABLE). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `fill` **Fill** · capability `mutate` · since 0.1.0 > Fill an input/textarea directly (fast, no per-character typing). Annotations: readOnly no · destructive no · idempotent yes · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `selector` | `string` | yes | — | min length 1 | | `value` | `string` | yes | — | — | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `selector` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`ELEMENT_NOT_ACTIONABLE`](/docs/reference/errors/#ELEMENT_NOT_ACTIONABLE). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `press_key` **Press key** · capability `mutate` · since 0.1.0 > Press a keyboard key. If a selector is given, focuses it first. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `key` | `string` | yes | — | min length 1 | | `selector` | `string` | no | — | min length 1 | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `key` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`ELEMENT_NOT_ACTIONABLE`](/docs/reference/errors/#ELEMENT_NOT_ACTIONABLE). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `hover` **Hover** · capability `mutate` · since 0.1.0 > Hover the mouse over an element. Annotations: readOnly no · destructive no · idempotent yes · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `selector` | `string` | yes | — | min length 1 | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `selector` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`ELEMENT_NOT_ACTIONABLE`](/docs/reference/errors/#ELEMENT_NOT_ACTIONABLE). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `select_option` **Select option** · capability `mutate` · since 0.1.0 > Select one or more options in a matched by selector. Every path MUST resolve under the server's uploads sandbox (/uploads/); out-of-tree paths fail with PATH_NOT_ALLOWED. Annotations: readOnly no · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `selector` | `string` | yes | — | min length 1 | | `paths` | `string[]` | yes | — | at least 1 item; each item: min length 1 | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `selector` | `string` | yes | | `ok` | `true` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`PATH_NOT_ALLOWED`](/docs/reference/errors/#PATH_NOT_ALLOWED), [`UPLOAD_FAILED`](/docs/reference/errors/#UPLOAD_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `download_file` **Download file** · capability `mutate` · since 0.1.0 > Click a trigger element and capture the resulting download into the managed downloads dir (/sessions//downloads/). Returns the absolute saved path, the suggested filename, and the byte size. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `trigger_selector` | `string` | yes | — | min length 1 | | `timeout` | `integer` | no | `30000` | ≥ 0 | | `tab_id` | `string` | no | — | — | | `save_as` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `session_id` | `string` | yes | | `saved_to` | `string` | yes | | `suggested_name` | `string` | yes | | `size` | `number` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`TAB_NOT_FOUND`](/docs/reference/errors/#TAB_NOT_FOUND), [`DOWNLOAD_FAILED`](/docs/reference/errors/#DOWNLOAD_FAILED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Auth states ### `save_storage_state` **Save storage state** · capability `mutate` · since 0.1.0 > Save the session's cookies + localStorage as a light "storage-state" snapshot for later restore via launch_session({ context_options: { storageState: name } }) in a non-persistent mode. Valid in any persistence mode. Annotations: readOnly no · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `name` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `name` | `string` | yes | | `path` | `string` | yes | | `size` | `number` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`PATH_NOT_ALLOWED`](/docs/reference/errors/#PATH_NOT_ALLOWED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `save_full_profile` **Save full profile** · capability `mutate` · since 0.1.0 > Save the session's full on-disk Chromium profile as a heavy "profile" snapshot (zipped user-data-dir) for later restore via launch_session({ persistence_mode: "persistent", restore_profile: name }). Only valid when the source session is in persistent mode. Annotations: readOnly no · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `name` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `name` | `string` | yes | | `path` | `string` | yes | | `size` | `number` | yes | Errors: [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED), [`INVALID_PERSISTENCE_CONFIG`](/docs/reference/errors/#INVALID_PERSISTENCE_CONFIG), [`PATH_NOT_ALLOWED`](/docs/reference/errors/#PATH_NOT_ALLOWED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `list_saved_auths` **List saved auths** · capability `read` · since 0.1.0 > List every saved auth snapshot (storage-state and full-profile) with kind, size, and when it was saved, most recent first. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: none. Result: a JSON array; each item has: | Field | Type | Always present | |---|---|---| | `name` | `string` | yes | | `kind` | one of `storage`, `profile` | yes | | `saved_at` | `number` | yes | | `size` | `number` | yes | Errors: none documented. Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Attention (HTTP transport only) ### `request_attention` **Request attention** · capability `attention` · since 0.1.0 > Flag a session for human attention and BLOCK until an operator resolves it in the admin dashboard, it times out, or it is cancelled. Both modes (takeover and notify) block until resolved — neither is fire-and-forget. Use when the agent is stuck on something only a human can do (CAPTCHA, interactive login, consent screen). Set max_wait_seconds to 0 to wait indefinitely (up to the server limit), which is best when a human may be away. Returns the operator decision { status, message?, resolved_by?, resolved_at, request_id }. http transport only. Annotations: readOnly no · destructive no · idempotent no · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `reason` | `string` | yes | — | min length 1 | | `mode` | one of `takeover`, `notify` | no | `"takeover"` | Both modes BLOCK until an operator resolves the request (neither is fire-and-forget). 'takeover' lets the operator drive the session live; 'notify' is view-only — the operator still acknowledges/resolves it without driving. | | `options` | `any` | no | — | — | | `max_wait_seconds` | `integer` | no | — | ≥ 0 | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `status` | one of `resolved`, `rejected`, `timeout`, `cancelled` | yes | | `message` | `string` | no | | `resolved_by` | `string` | no | | `resolved_at` | `number` or `null` | yes | | `request_id` | `string` | yes | Errors: [`ATTENTION_REQUIRES_HTTP`](/docs/reference/errors/#ATTENTION_REQUIRES_HTTP), [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `get_attention_result` **Get attention result** · capability `attention` · since 0.1.0 > Retrieve the outcome of a prior request_attention by its request_id. Returns immediately if the request is already resolved/rejected/timed-out; otherwise BLOCKS like request_attention until it settles. Use to recover a decision after a dropped connection. http transport only. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `request_id` | `string` | yes | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `status` | one of `resolved`, `rejected`, `timeout`, `cancelled` | yes | | `message` | `string` | no | | `resolved_by` | `string` | no | | `resolved_at` | `number` or `null` | yes | | `request_id` | `string` | yes | Errors: [`ATTENTION_REQUIRES_HTTP`](/docs/reference/errors/#ATTENTION_REQUIRES_HTTP). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ## Vault ### `vault_list_available` **Vault: list available** · capability `credential` · since 0.1.0 > List the vault entries you may fill on the page this session is currently on. Returns { entries: [{ entry_name, allowed_origins, redact_username, require_no_evaluate }], scope, scoped_to, note? }. Results are SCOPED to the session's current page — navigate to the login page first, then call this. Pass `url` = the domain of that login page (e.g. "github.com"); if it does not match the page the session is actually on, the request is denied and reported. An entry only appears after an operator authorizes this session for it. Never returns secrets. Annotations: readOnly yes · destructive no · idempotent yes · openWorld no Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `url` | `string` | no | — | The domain (or URL) of the login page you have navigated to, e.g. "github.com". Results are scoped to this site. Pass only the domain — not the full URL with its path/query — the session already holds the exact page. If it does not match the page the session is actually on, the request is denied and reported. | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `entries` | `object[]` | yes | | `scope` | one of `unscoped`, `page`, `no_page`, `rejected` | yes | | `scoped_to` | `string` or `null` | yes | | `mismatch` | `object` | no | | `note` | `string` | no | Errors: [`VAULT_NOT_CONFIGURED`](/docs/reference/errors/#VAULT_NOT_CONFIGURED), [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). ### `vault_fill` **Vault: fill** · capability `credential` · since 0.1.0 > Atomically inject a vault credential into a login form: origin re-check, fetch from the backend, fill username + password, optional submit, then redact. `entry_name` is the stable handle from vault_list_available. The filled values are LEFT in the form by default — pass `clear_after_fill: true` only if you want the inputs wiped after the fill (done after `after_submit_wait_ms`, so an async/AJAX submit still reads them). Returns { status: "success" | "origin_mismatch" | "auth_failed" | "blocked", redacted: true, reason? }. The credential never appears in the response, logs, events, or screenshots. The origin is checked against the entry allow-list by registrable domain. Annotations: readOnly no · destructive no · idempotent no · openWorld yes Parameters: | Name | Type | Required | Default | Notes | |---|---|---|---|---| | `session_id` | `string` | yes | — | — | | `entry_name` | `string` | yes | — | min length 1 | | `username_selector` | `string` | yes | — | min length 1 | | `password_selector` | `string` | yes | — | min length 1 | | `submit_selector` | `string` | no | — | min length 1 | | `after_submit_wait_ms` | `integer` | no | — | ≥ 0 | | `clear_after_fill` | `boolean` | no | — | — | | `tab_id` | `string` | no | — | — | Result (JSON text block, mirrored in `structuredContent`): | Field | Type | Always present | |---|---|---| | `status` | one of `success`, `origin_mismatch`, `auth_failed`, `blocked` | yes | | `redacted` | `true` | yes | | `reason` | `string` | no | Errors: [`VAULT_NOT_CONFIGURED`](/docs/reference/errors/#VAULT_NOT_CONFIGURED), [`VAULT_LOCKED`](/docs/reference/errors/#VAULT_LOCKED), [`SESSION_NOT_FOUND`](/docs/reference/errors/#SESSION_NOT_FOUND), [`SESSION_DEAD`](/docs/reference/errors/#SESSION_DEAD), [`SESSION_ACCESS_DENIED`](/docs/reference/errors/#SESSION_ACCESS_DENIED). Any tool may also return [`INVALID_ARGUMENTS`](/docs/reference/errors/#INVALID_ARGUMENTS) and [`INTERNAL_ERROR`](/docs/reference/errors/#INTERNAL_ERROR). --- # Error reference Source: https://browserhive.ai/docs/reference/errors/ Every error code BrowserHive can produce (98 codes), generated from `ERROR_REGISTRY` in `@browserhive/contracts/errors`. Each code has a stable anchor: `errors.md#`, which is also the `type` URL of HTTP problem responses (`https://browserhive.ai/docs/errors#`). ## How errors reach you - **MCP tools:** the result has `isError: true` and a text block `[CODE] message` (a stable text format clients may parse). The structured form `{ code, message, retryable, hint?, details? }` is in `_meta["browserhive.ai/error"]`. - **REST API:** `application/problem+json` (RFC 9457) with `type`, `title`, `status`, `detail`, plus `code`, `retryable`, `hint`, `details` and `request_id`. - **WebSocket:** a frame with `kind: "error"` and payload `{ code, title, hint?, details?, request_id? }`; the `corr` of the failed command is echoed. - **CLI:** boot errors print `browserhive: [CODE] message` to stderr and exit with the code listed per entry. ## Retry guidance | `retryable` | Meaning | |---|---| | `never` | do not retry; the request cannot succeed as sent | | `immediate` | safe to retry right away | | `backoff` | retry later with backoff | | `after_operator` | retry after an operator acts (unlock the vault, resolve a request, change policy) | | `different_args` | retry only with different arguments | ## Domain errors Returned by tools and the REST API when a request cannot be served (unknown session, blocked URL, vault locked…). | Code | Title | HTTP | Retryable | |---|---|---|---| | [`SESSION_NOT_FOUND`](#SESSION_NOT_FOUND) | Session not found | 404 | different_args | | [`SESSION_ALREADY_EXISTS`](#SESSION_ALREADY_EXISTS) | Session id collision | 409 | immediate | | [`SESSION_DEAD`](#SESSION_DEAD) | Session is dead | 410 | different_args | | [`SESSION_LIMIT_REACHED`](#SESSION_LIMIT_REACHED) | Session limit reached | 429 | backoff | | [`SESSION_ACCESS_DENIED`](#SESSION_ACCESS_DENIED) | Session not found | 404 | never | | [`SESSION_NOT_AVAILABLE`](#SESSION_NOT_AVAILABLE) | Session not available | 409 | after_operator | | [`SESSION_NOT_LIVE`](#SESSION_NOT_LIVE) | Session is not live | 409 | never | | [`SESSION_LIVE`](#SESSION_LIVE) | Session is still live | 409 | never | | [`UNKNOWN_CHANNEL`](#UNKNOWN_CHANNEL) | Unknown browser channel | 400 | different_args | | [`INVALID_SLUG`](#INVALID_SLUG) | Invalid slug | 400 | different_args | | [`UNSAFE_LAUNCH_ARG`](#UNSAFE_LAUNCH_ARG) | Launch argument denied | 400 | different_args | | [`INVALID_PERSISTENCE_CONFIG`](#INVALID_PERSISTENCE_CONFIG) | Invalid persistence config | 400 | different_args | | [`TAB_NOT_FOUND`](#TAB_NOT_FOUND) | Tab not found | 404 | different_args | | [`PATH_NOT_ALLOWED`](#PATH_NOT_ALLOWED) | Path outside the sandbox | 400 | different_args | | [`AUTH_STATE_NOT_FOUND`](#AUTH_STATE_NOT_FOUND) | Saved auth state not found | 404 | different_args | | [`EVALUATE_DISABLED`](#EVALUATE_DISABLED) | evaluate is disabled | 403 | never | | [`ELEMENT_NOT_ACTIONABLE`](#ELEMENT_NOT_ACTIONABLE) | Element not actionable | 422 | backoff | | [`ELEMENT_NOT_FOUND`](#ELEMENT_NOT_FOUND) | Element not found | 404 | different_args | | [`NAVIGATION_TIMEOUT`](#NAVIGATION_TIMEOUT) | Navigation timed out | 504 | backoff | | [`NAVIGATION_FAILED`](#NAVIGATION_FAILED) | Navigation failed | 502 | backoff | | [`WAIT_TIMEOUT`](#WAIT_TIMEOUT) | Wait timed out | 504 | backoff | | [`SCRIPT_ERROR`](#SCRIPT_ERROR) | Script threw | 422 | different_args | | [`DOWNLOAD_FAILED`](#DOWNLOAD_FAILED) | Download failed | 502 | backoff | | [`UPLOAD_FAILED`](#UPLOAD_FAILED) | Upload failed | 422 | different_args | | [`PAGE_CLOSED`](#PAGE_CLOSED) | Page closed | 410 | different_args | | [`BROWSER_CRASHED`](#BROWSER_CRASHED) | Browser crashed | 500 | after_operator | | [`URL_BLOCKED`](#URL_BLOCKED) | URL blocked by policy | 403 | never | | [`BROWSER_NOT_INSTALLED`](#BROWSER_NOT_INSTALLED) | Browser not installed | 503 | after_operator | | [`VAULT_NOT_CONFIGURED`](#VAULT_NOT_CONFIGURED) | No vault configured | 404 | never | | [`VAULT_LOCKED`](#VAULT_LOCKED) | Vault locked | 409 | after_operator | | [`VAULT_ENTRY_NOT_FOUND`](#VAULT_ENTRY_NOT_FOUND) | Vault entry not found | 404 | different_args | | [`VAULT_NOT_AUTHORIZED`](#VAULT_NOT_AUTHORIZED) | Vault entry not authorized | 403 | never | | [`EVALUATE_REQUIRED_OFF`](#EVALUATE_REQUIRED_OFF) | Entry requires evaluate disabled | 403 | never | | [`DASHBOARD_DENIED`](#DASHBOARD_DENIED) | Fill denied by operator | 403 | after_operator | | [`VAULT_UNLOCK_FAILED`](#VAULT_UNLOCK_FAILED) | Vault unlock failed | 401 | different_args | | [`VAULT_SYNC_UNSUPPORTED`](#VAULT_SYNC_UNSUPPORTED) | Vault sync unsupported | 400 | never | | [`VAULT_BACKEND_ERROR`](#VAULT_BACKEND_ERROR) | Vault backend error | 502 | backoff | | [`ATTENTION_REQUIRES_HTTP`](#ATTENTION_REQUIRES_HTTP) | Attention requires http | 400 | never | | [`ATTENTION_NOT_OPEN`](#ATTENTION_NOT_OPEN) | Attention request is not open | 409 | never | | [`CONFIRM_NOT_OPEN`](#CONFIRM_NOT_OPEN) | Confirmation is not open | 409 | never | | [`INPUT_NOT_PERMITTED`](#INPUT_NOT_PERMITTED) | Input not permitted | 409 | after_operator | | [`SCREENCAST_FAILED`](#SCREENCAST_FAILED) | Screencast failed | 502 | backoff | | [`TOOL_NOT_AVAILABLE`](#TOOL_NOT_AVAILABLE) | Tool not available | 400 | never | | [`INVALID_ARGUMENTS`](#INVALID_ARGUMENTS) | Invalid arguments | 400 | different_args | | [`TRACE_UNAVAILABLE`](#TRACE_UNAVAILABLE) | Trace unavailable | 404 | never | | [`SCREENSHOT_UNAVAILABLE`](#SCREENSHOT_UNAVAILABLE) | Screenshot unavailable | 404 | never | | [`INTERNAL_ERROR`](#INTERNAL_ERROR) | Internal error | 500 | backoff | ### `SESSION_NOT_FOUND` | Property | Value | |---|---| | Title | Session not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `No browser session with id '{session_id}'` Hint: Use list_sessions to see live sessions. Cause: The session id is unknown, mistyped, or the session was already closed and removed. Resolution: Call list_sessions and use one of the returned ids, or launch a new session. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SESSION_ALREADY_EXISTS` | Property | Value | |---|---| | Title | Session id collision | | HTTP status | 409 | | Category | `domain` | | Retryable | `immediate` (safe to retry right away) | Message: `Session '{session_id}' already exists. Slug+nanoid collision is exceptionally rare; the caller should retry.` Hint: Retry launch_session; a fresh id is generated on every call. Cause: The generated `-` id collided with a live session (36^8 space). Resolution: Retry immediately; a collision twice in a row indicates a broken id generator. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SESSION_DEAD` | Property | Value | |---|---| | Title | Session is dead | | HTTP status | 410 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Session '{session_id}' is dead — its underlying browser process crashed.` Hint: Close the session and launch a new one. Cause: The browser context or process disconnected; the session is awaiting reaping. Resolution: Launch a replacement session; the crashed one is closed with reason `crash`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SESSION_LIMIT_REACHED` | Property | Value | |---|---| | Title | Session limit reached | | HTTP status | 429 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Concurrent session limit reached (max={limit}). Close a session and retry.` Hint: Close an idle session or wait for a lease to expire, then retry. Cause: The number of live sessions equals `maxSessions` (derived from host RAM by default). Resolution: Close sessions you no longer need, or raise `maxSessions` on a host with more memory. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `limit` | `integer` | yes | — | | `live` | `integer` | yes | — | ### `SESSION_ACCESS_DENIED` | Property | Value | |---|---| | Title | Session not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `No browser session with id '{session_id}'` Hint: Use list_sessions to see the sessions you own. Cause: The session belongs to a different principal and ownership enforcement is on (`auth=token`). Resolution: Only the owning principal may use a session; launch your own. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SESSION_NOT_AVAILABLE` | Property | Value | |---|---| | Title | Session not available | | HTTP status | 409 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `Session '{session_id}' is not available (state: {state}).` Hint: Wait until the session is live, or pick another session. Cause: The session is launching, draining, paused for an operator, closed or crashed. Resolution: Retry once the session state returns to `live`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `state` | `string` | yes | — | ### `SESSION_NOT_LIVE` | Property | Value | |---|---| | Title | Session is not live | | HTTP status | 409 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Session '{session_id}' is not live.` Hint: This action needs a live session; the session has ended. Cause: An operator action that needs a running browser (live view, input, terminate) targeted a closed session. Resolution: Open the trace or history views instead. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SESSION_LIVE` | Property | Value | |---|---| | Title | Session is still live | | HTTP status | 409 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Session '{session_id}' is still live.` Hint: Terminate or close the session first. Cause: An action that needs a finished session (archive, delete, trace download) targeted a live one. Resolution: Close the session, then retry. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `UNKNOWN_CHANNEL` | Property | Value | |---|---| | Title | Unknown browser channel | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Unknown browser channel '{channel}'. Expected one of: chromium, chrome, edge.` Hint: Pass channel as one of chromium, chrome or edge. Cause: The `channel` argument is not a supported Chromium-family channel. Resolution: Use `chromium` (bundled), or `chrome`/`edge` when that browser is installed on the host. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `channel` | `string` | yes | — | | `supported` | `string[]` | yes | — | ### `INVALID_SLUG` | Property | Value | |---|---| | Title | Invalid slug | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Invalid slug '{slug}'. Slugs must match /^[a-z][a-z0-9-]{1,31}$/ (start with a lowercase letter, 2–32 chars, lowercase alphanumerics and dashes).` Hint: Use a short lowercase name such as "shop" or "docs-crawl". Cause: The slug does not match the filesystem-safe grammar. Resolution: Lowercase letters, digits and dashes only; start with a letter; 2–32 characters. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `slug` | `string` | yes | — | | `pattern` | `string` | yes | — | ### `UNSAFE_LAUNCH_ARG` | Property | Value | |---|---| | Title | Launch argument denied | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Launch arg '{arg}' is on the deny-list and would break session isolation.` Hint: Remove the argument from launch_options.args. Cause: A Chromium flag that changes the profile directory, sandbox or debugging surface was passed. Resolution: Use the persistence modes and channel options instead of raw isolation-breaking flags. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `arg` | `string` | yes | — | ### `INVALID_PERSISTENCE_CONFIG` | Property | Value | |---|---| | Title | Invalid persistence config | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Invalid persistence config: {reason}` Hint: Check the persistence_mode / restore_profile / storage_state / incognito combination. Cause: The requested combination of persistence mode and launch options is contradictory. Resolution: See the persistence matrix in the tool reference and pick a consistent combination. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `reason` | `string` | yes | — | ### `TAB_NOT_FOUND` | Property | Value | |---|---| | Title | Tab not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Tab '{tab_id}' not found in session '{session_id}'.` Hint: Use list_tabs to see open tabs. Cause: The tab id is unknown or the tab was closed; `` means the session has no open tab. Resolution: Call list_tabs, or open a tab with new_tab. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `tab_id` | `string` | yes | — | ### `PATH_NOT_ALLOWED` | Property | Value | |---|---| | Title | Path outside the sandbox | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Path '{path}' is outside the allowed sandbox roots.` Hint: Use a relative path or an absolute path under an allowed root. Cause: The resolved path (symlinks followed) escapes the session directory and the uploads directory. Resolution: Write inside the session directory (relative paths resolve there) or the uploads directory. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | | `roots` | `string[]` | yes | — | ### `AUTH_STATE_NOT_FOUND` | Property | Value | |---|---| | Title | Saved auth state not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `No saved {kind_label} snapshot named '{name}'. Use list_saved_auths to see what is available.` Hint: Call list_saved_auths and use one of the returned names. Cause: No snapshot of the requested kind exists under that name in the auth-states directory. Resolution: Save one with save_storage_state or save_full_profile first. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `name` | `string` | yes | — | | `kind` | one of `storage`, `profile` | yes | — | ### `EVALUATE_DISABLED` | Property | Value | |---|---| | Title | evaluate is disabled | | HTTP status | 403 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The 'evaluate' tool is disabled for session '{session_id}'.` Hint: Use the structured tools (click, fill, get_content, snapshot) instead. Cause: The session was launched with `disable_evaluate: true`, or the server runs with `allowEvaluate=false`. Resolution: Relaunch without `disable_evaluate`, or ask the operator to enable `allowEvaluate`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `scope` | one of `session`, `server` | yes | — | ### `ELEMENT_NOT_ACTIONABLE` | Property | Value | |---|---| | Title | Element not actionable | | HTTP status | 422 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Element '{selector}' did not become actionable within the timeout — it may be hidden, detached, disabled, covered by another element, or the selector matched the wrong element. Use a more specific, visible selector, or raise the timeout.` Hint: Take a snapshot to find a visible, unique selector; raise timeout_ms if the page is slow. Cause: The target was hidden, detached, disabled, covered, or the selector matched the wrong element. Resolution: Use a more specific selector or wait for the element to become visible first. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `selector` | `string` | yes | — | | `detail` | `string` | no | — | ### `ELEMENT_NOT_FOUND` | Property | Value | |---|---| | Title | Element not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `No element matched selector '{selector}'.` Hint: Take a snapshot and pick a selector that matches exactly one element. Cause: The selector matched nothing, or matched several elements under strict mode. Resolution: Refine the selector; `count` in details tells you how many matched. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `selector` | `string` | yes | — | | `count` | `integer` | no | — | ### `NAVIGATION_TIMEOUT` | Property | Value | |---|---| | Title | Navigation timed out | | HTTP status | 504 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Navigation to '{url}' did not finish within {timeout_ms} ms.` Hint: Retry with a longer timeout_ms or a lighter wait_until (commit, domcontentloaded). Cause: The page did not reach the requested load state before the timeout. Resolution: Raise the timeout, choose an earlier `wait_until`, or check the network. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `url` | `string` | yes | — | | `timeout_ms` | `integer` | yes | — | ### `NAVIGATION_FAILED` | Property | Value | |---|---| | Title | Navigation failed | | HTTP status | 502 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Navigation to '{url}' failed ({net_error}).` Hint: Check the URL and network; net_error names the Chromium failure. Cause: Chromium reported a network error (`net::ERR_*`): DNS, TLS, connection refused, and so on. Resolution: Verify the URL resolves and is reachable from the host; retry transient failures. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `url` | `string` | yes | — | | `net_error` | `string` | yes | — | ### `WAIT_TIMEOUT` | Property | Value | |---|---| | Title | Wait timed out | | HTTP status | 504 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Timed out after {timeout_ms} ms waiting for {what}.` Hint: Raise timeout_ms or wait for a different condition. Cause: The awaited selector state, URL or load state did not occur in time. Resolution: Check the page state with snapshot, then wait for a condition that will happen. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `what` | `string` | yes | — | | `timeout_ms` | `integer` | yes | — | ### `SCRIPT_ERROR` | Property | Value | |---|---| | Title | Script threw | | HTTP status | 422 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `The evaluated script threw: {message}` Hint: Fix the script; the first line of the page error is in details.message. Cause: The expression passed to `evaluate` threw inside the page. Resolution: Correct the script or guard against missing globals. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `message` | `string` | yes | — | ### `DOWNLOAD_FAILED` | Property | Value | |---|---| | Title | Download failed | | HTTP status | 502 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Download failed: {reason}` Hint: Retry; if it persists, check that the link starts a download. Cause: The browser reported the download as failed or cancelled. Resolution: Retry the download; verify the target actually serves a file. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `reason` | `string` | yes | — | ### `UPLOAD_FAILED` | Property | Value | |---|---| | Title | Upload failed | | HTTP status | 422 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Upload failed: {reason}` Hint: Check that the selector targets a file input and the file exists under uploads/. Cause: `setInputFiles` failed: wrong element, missing file, or a page that rejected the input. Resolution: Target an `` and place the file in the uploads directory. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `reason` | `string` | yes | — | ### `PAGE_CLOSED` | Property | Value | |---|---| | Title | Page closed | | HTTP status | 410 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Tab '{tab_id}' was closed while the action was running.` Hint: Use list_tabs and target an open tab. Cause: The page or its execution context was destroyed mid-call (closed tab, navigation away). Resolution: Re-open or re-target the tab and retry. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `tab_id` | `string` | yes | — | ### `BROWSER_CRASHED` | Property | Value | |---|---| | Title | Browser crashed | | HTTP status | 500 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `The browser behind session '{session_id}' crashed.` Hint: Launch a new session; the crashed one will be reaped. Cause: The browser context or process was closed unexpectedly. Resolution: Launch a replacement session; check host memory if it repeats. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `URL_BLOCKED` | Property | Value | |---|---| | Title | URL blocked by policy | | HTTP status | 403 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The URL '{url}' is blocked by the administrator (matched the blocklist pattern '{pattern}'). This is an operator policy, not a transient failure — do not retry this URL, and do not try to reach it by another route. Report it to the user if the task cannot continue.` Hint: Do not retry; report the block to the user. Cause: The URL matched a pattern in the operator blocklist (`blocklist` file). Resolution: Operators edit the blocklist file (reloaded live) if the block is unintended. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `url` | `string` | yes | — | | `pattern` | `string` | yes | — | ### `BROWSER_NOT_INSTALLED` | Property | Value | |---|---| | Title | Browser not installed | | HTTP status | 503 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `No browser is installed for channel '{channel}'. Run '{install_command}' on the host.` Hint: Ask the operator to run browserhive init. Cause: The Chromium binary for the driver/channel is missing (no postinstall download, D-18). Resolution: Run `browserhive init`, or install the branded browser for `chrome`/`edge`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `channel` | `string` | yes | — | | `install_command` | `string` | yes | — | ### `VAULT_NOT_CONFIGURED` | Property | Value | |---|---| | Title | No vault configured | | HTTP status | 404 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `No vault backend is configured. Start the server with --vault .` Hint: Ask the operator to start the server with vault=bitwarden. Cause: The server runs with `vault=off`. Resolution: Start with `--vault bitwarden` and unlock it. Details: none. ### `VAULT_LOCKED` | Property | Value | |---|---| | Title | Vault locked | | HTTP status | 409 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `Vault backend is locked. Unlock it on the dashboard Vault page.` Hint: Ask the operator to unlock the vault, then retry. Cause: The backend session token is absent or expired. Resolution: Run `bw unlock --raw` and paste the session token on the dashboard Vault page, or restart the server with `BW_SESSION` exported. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `backend` | `string` | yes | — | ### `VAULT_ENTRY_NOT_FOUND` | Property | Value | |---|---| | Title | Vault entry not found | | HTTP status | 404 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Vault entry '{entry_name}' is not in the backend.` Hint: Use vault_list_available to see entries you may fill. Cause: No binding or backend item matches the requested entry name. Resolution: Pick a name from vault_list_available, or ask the operator to bind the item. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `entry_name` | `string` | yes | — | ### `VAULT_NOT_AUTHORIZED` | Property | Value | |---|---| | Title | Vault entry not authorized | | HTTP status | 403 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Vault entry '{entry_name}' is not authorized for this session's slug.` Hint: The operator controls which sessions may use this entry. Cause: The binding or group policy does not allow this principal and session slug. Resolution: Operators edit the binding (slug globs, principals) on the Vault page. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `entry_name` | `string` | yes | — | ### `EVALUATE_REQUIRED_OFF` | Property | Value | |---|---| | Title | Entry requires evaluate disabled | | HTTP status | 403 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Vault entry '{entry_name}' requires the session to have evaluate disabled (launch with disable_evaluate: true).` Hint: Relaunch the session with disable_evaluate: true. Cause: The binding is marked `require_no_evaluate` and the session can run scripts. Resolution: Launch with `disable_evaluate: true` before filling this entry. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `entry_name` | `string` | yes | — | ### `DASHBOARD_DENIED` | Property | Value | |---|---| | Title | Fill denied by operator | | HTTP status | 403 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `Vault fill for '{entry_name}' was denied by the dashboard (or timed out waiting for confirmation).` Hint: Ask the operator to approve the fill, then retry. Cause: The operator rejected the confirmation, or nobody answered before the deadline. Resolution: Retry when an operator is available to confirm. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | | `entry_name` | `string` | no | — | ### `VAULT_UNLOCK_FAILED` | Property | Value | |---|---| | Title | Vault unlock failed | | HTTP status | 401 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Vault unlock failed ({mode}).` Hint: Create a new session token with `bw unlock --raw` and paste it again. Cause: The backend rejected the session token (expired after `bw lock` or `bw logout`, or from another `bw` login), or the passphrase of a passphrase backend. Resolution: Run `bw unlock --raw` as the user that runs BrowserHive and paste the new token, or export it as `BW_SESSION` before start. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `mode` | `string` | yes | — | ### `VAULT_SYNC_UNSUPPORTED` | Property | Value | |---|---| | Title | Vault sync unsupported | | HTTP status | 400 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Vault backend '{backend}' does not support sync.` Hint: Nothing to do; this backend has no sync operation. Cause: The backend capabilities report `sync: false`. Resolution: Use a backend that supports sync, or skip the call. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `backend` | `string` | yes | — | ### `VAULT_BACKEND_ERROR` | Property | Value | |---|---| | Title | Vault backend error | | HTTP status | 502 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Vault backend '{backend}' failed ({kind}).` Hint: Retry; if it persists, check the backend CLI on the host. Cause: The backend CLI is missing, timed out, or exited with an error. Resolution: Install or repair the backend CLI (`bw`) and retry. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `backend` | `string` | yes | — | | `kind` | one of `not_installed`, `timeout`, `exit` | yes | — | ### `ATTENTION_REQUIRES_HTTP` | Property | Value | |---|---| | Title | Attention requires http | | HTTP status | 400 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `'{tool}' requires the http transport; human-in-the-loop attention is not available under stdio.` Hint: Run the server with transport=http and admin=true. Cause: Attention needs the shared, long-lived HTTP daemon and the dashboard; stdio is single-client. Resolution: Start BrowserHive with `--admin` (http transport). Details: | Field | Type | Required | Constraints | |---|---|---|---| | `tool` | `string` | yes | — | ### `ATTENTION_NOT_OPEN` | Property | Value | |---|---| | Title | Attention request is not open | | HTTP status | 409 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Attention request '{request_id}' is not open (status: {status}).` Hint: Refresh the queue. Cause: The request was already resolved, rejected, timed out or cancelled. Resolution: Nothing to do; the outcome is final. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | | `status` | `string` | yes | — | ### `CONFIRM_NOT_OPEN` | Property | Value | |---|---| | Title | Confirmation is not open | | HTTP status | 409 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Vault confirmation '{request_id}' is not open (status: {status}).` Hint: Refresh the confirm queue. Cause: The confirmation was already decided or expired. Resolution: Nothing to do; the outcome is final. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | | `status` | `string` | yes | — | ### `INPUT_NOT_PERMITTED` | Property | Value | |---|---| | Title | Input not permitted | | HTTP status | 409 | | Category | `domain` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `Input is not permitted on session '{session_id}': no takeover attention request is open.` Hint: Input is only accepted while a takeover attention request is open. Cause: Live-view input is gated on an open `takeover` request from the agent. Resolution: Wait for the agent to request attention in takeover mode. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | ### `SCREENCAST_FAILED` | Property | Value | |---|---| | Title | Screencast failed | | HTTP status | 502 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Screencast for session '{session_id}' failed: {reason}` Hint: Retry; if the session is closing the stream cannot start. Cause: The CDP screencast could not be started or the bridge lost its page. Resolution: Retry the live view; check the session is live. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `reason` | `string` | yes | — | ### `TOOL_NOT_AVAILABLE` | Property | Value | |---|---| | Title | Tool not available | | HTTP status | 400 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Tool '{tool}' is not available on this server (requires {requires}).` Hint: The tool needs a server feature that is off. Cause: The tool pack requires a transport, vault or admin feature that is not enabled. Resolution: Enable the feature on the server, or avoid the tool. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `tool` | `string` | yes | — | | `requires` | `string` | yes | — | ### `INVALID_ARGUMENTS` | Property | Value | |---|---| | Title | Invalid arguments | | HTTP status | 400 | | Category | `domain` | | Retryable | `different_args` (retry only with different arguments) | Message: `Invalid arguments: {summary}` Hint: Fix the listed issues and call the tool again. Cause: The tool arguments failed schema validation. Resolution: Each issue names the path and what is wrong. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `issues` | `object[]` | yes | each item: keys `path`, `message` | ### `TRACE_UNAVAILABLE` | Property | Value | |---|---| | Title | Trace unavailable | | HTTP status | 404 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `No trace is available for this session.` Hint: Tracing is on when trace=true (default with admin). Cause: Tracing was off for the session, or the trace failed to finalize. Resolution: Enable `trace` and relaunch; see TRACE_START_FAILED warnings. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `enabled` | `boolean` | yes | — | ### `SCREENSHOT_UNAVAILABLE` | Property | Value | |---|---| | Title | Screenshot unavailable | | HTTP status | 404 | | Category | `domain` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `No screenshot is stored for event '{event_id}'.` Hint: Screenshots are kept per tool call while retention allows. Cause: The event produced no screenshot, or retention removed it. Resolution: Nothing to do; the artifact no longer exists. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `event_id` | `string` | yes | — | ### `INTERNAL_ERROR` | Property | Value | |---|---| | Title | Internal error | | HTTP status | 500 | | Category | `domain` | | Retryable | `backoff` (retry later with backoff) | Message: `Internal error (ref {ref}).` Hint: Quote the ref to the operator; the logs hold the detail. Cause: An unexpected failure that no typed code describes. Resolution: Search the logs for the ref (request id) and file a bug if it repeats. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `ref` | `string` | yes | — | ## Boot errors Stop the process before it serves anything. The CLI prints `[CODE] message` and exits with the listed exit code. | Code | Title | HTTP | Retryable | |---|---|---|---| | [`ADMIN_REQUIRES_HTTP`](#ADMIN_REQUIRES_HTTP) | Dashboard requires http | 500 | never | | [`INSECURE_BIND_REFUSED`](#INSECURE_BIND_REFUSED) | Insecure bind refused | 500 | never | | [`PORT_IN_USE`](#PORT_IN_USE) | Port in use | 500 | never | | [`BIND_FAILED`](#BIND_FAILED) | Bind failed | 500 | never | | [`CONFIG_INVALID`](#CONFIG_INVALID) | Invalid configuration value | 400 | never | | [`CONFIG_UNKNOWN_KEY`](#CONFIG_UNKNOWN_KEY) | Unknown configuration key | 400 | never | | [`BLOCKLIST_LOAD_FAILED`](#BLOCKLIST_LOAD_FAILED) | Blocklist could not be loaded | 400 | never | | [`DATA_DIR_UNWRITABLE`](#DATA_DIR_UNWRITABLE) | Data directory not writable | 500 | never | | [`DATA_DIR_LOCKED`](#DATA_DIR_LOCKED) | Data directory in use | 409 | never | | [`DB_OPEN_FAILED`](#DB_OPEN_FAILED) | Database could not be opened | 500 | never | | [`DB_NEWER_THAN_BINARY`](#DB_NEWER_THAN_BINARY) | Database newer than this version | 500 | never | | [`MIGRATION_FAILED`](#MIGRATION_FAILED) | Migration failed | 500 | never | | [`DB_CORRUPT`](#DB_CORRUPT) | Database corrupt | 500 | never | | [`UNHANDLED`](#UNHANDLED) | Unhandled error | 500 | never | | [`RETENTION_FAILED`](#RETENTION_FAILED) | Retention sweep failed | 500 | backoff | | [`STALE_BROWSER_PROCESSES`](#STALE_BROWSER_PROCESSES) | Stale browser processes found | 500 | never | ### `ADMIN_REQUIRES_HTTP` | Property | Value | |---|---| | Title | Dashboard requires http | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `admin=true requires transport=http. The dashboard is not available under stdio.` Hint: Drop admin=true, or use transport=http. Cause: `admin=true` was combined with `transport=stdio`; stdio is single-client and cannot host the dashboard. Resolution: Run with the http transport (the default) to use the dashboard. Details: none. ### `INSECURE_BIND_REFUSED` | Property | Value | |---|---| | Title | Insecure bind refused | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `Refusing to bind {host} without authentication. Set auth=token, or set allowInsecureBind=true to accept the risk.` Hint: Set auth=token for LAN exposure. Cause: A non-loopback `host` was requested without `auth=token` and without `allowInsecureBind=true`. Resolution: Enable token auth, or acknowledge the risk with `allowInsecureBind=true`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `host` | `string` | yes | — | ### `PORT_IN_USE` | Property | Value | |---|---| | Title | Port in use | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `Cannot listen on {host}:{port}: the port is already in use ({errno}).` Hint: Pick another port with --port, or stop the process holding it. Cause: Another process (often a previous BrowserHive) is bound to the same host and port. Resolution: Stop the other process or choose a free port. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `host` | `string` | yes | — | | `port` | `integer` | yes | — | | `errno` | `string` | yes | — | ### `BIND_FAILED` | Property | Value | |---|---| | Title | Bind failed | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `Cannot listen on {host}:{port} ({errno}).` Hint: Check the address exists on this host and the port is permitted. Cause: The listener could not bind for a reason other than a busy port (EACCES, EADDRNOTAVAIL…). Resolution: Use an address assigned to this host and an unprivileged port. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `host` | `string` | yes | — | | `port` | `integer` | yes | — | | `errno` | `string` | yes | — | ### `CONFIG_INVALID` | Property | Value | |---|---| | Title | Invalid configuration value | | HTTP status | 400 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 64 | Message: `invalid value for {source}: {reason}` Hint: Run 'browserhive config validate' to see every problem. Cause: A value failed its grammar, range or a cross-field rule. Resolution: Fix the value at the named source; the grammar is in the message. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `key` | `string` | yes | — | | `source` | `string` | yes | — | | `reason` | `string` | yes | — | ### `CONFIG_UNKNOWN_KEY` | Property | Value | |---|---| | Title | Unknown configuration key | | HTTP status | 400 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 64 | Message: `unknown key '{key}' in {source}.` Hint: Run 'browserhive --help' for the list of keys. Cause: A flag, environment variable or config-file key is not in the registry (typo or removed knob). Resolution: Use the suggested name; kebab-case flag spellings are not supported. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `key` | `string` | yes | — | | `source` | `string` | yes | — | | `suggestion` | `string` | no | — | ### `BLOCKLIST_LOAD_FAILED` | Property | Value | |---|---| | Title | Blocklist could not be loaded | | HTTP status | 400 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `Cannot load blocklist {path}: {reason}` Hint: Check the path and file permissions. Cause: The configured blocklist file is missing, unreadable or over the entry cap. Resolution: Fix the file; a configured-but-unreadable blocklist is fatal by design. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | | `reason` | `string` | yes | — | ### `DATA_DIR_UNWRITABLE` | Property | Value | |---|---| | Title | Data directory not writable | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `The data directory {path} is not writable.` Hint: Fix permissions or choose another dataDir. Cause: The data directory could not be created with mode 0700 or is owned by another user. Resolution: Point `dataDir` at a directory the server user owns. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | ### `DATA_DIR_LOCKED` | Property | Value | |---|---| | Title | Data directory in use | | HTTP status | 409 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `The data directory {path} is in use by another BrowserHive process (pid {pid}).` Hint: Stop the running server first, or point dataDir at another directory. Cause: A live process holds `/browserhive.lock`; one server (or one maintenance command) owns a data directory at a time. Resolution: Stop that process. A lock left by a crashed process is detected (its pid is gone) and replaced automatically. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | | `pid` | `integer` | yes | — | ### `DB_OPEN_FAILED` | Property | Value | |---|---| | Title | Database could not be opened | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `Cannot open database {path}: {reason}` Hint: Run 'browserhive db status' for details. Cause: SQLite refused to open the file (locked, permissions, wrong application_id). Resolution: Check for another running server and file permissions. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | | `reason` | `string` | yes | — | ### `DB_NEWER_THAN_BINARY` | Property | Value | |---|---| | Title | Database newer than this version | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 3 | Message: `The database schema (v{db_version}, readable from v{min_reader_version}) is newer than this binary supports (v{binary_version}).` Hint: Upgrade BrowserHive, or restore the pre-upgrade backup with 'browserhive db restore '. Cause: A newer BrowserHive migrated the database past this binary’s compatibility window (D-04). Resolution: Upgrade, or restore the named backup to downgrade. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `db_version` | `integer` | yes | — | | `min_reader_version` | `integer` | yes | — | | `binary_version` | `integer` | yes | — | | `backup_path` | `string` | no | — | ### `MIGRATION_FAILED` | Property | Value | |---|---| | Title | Migration failed | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `Migration '{name}' (v{from} → v{to}) failed. The pre-migration backup is at {backup_path}.` Hint: The database was rolled back; report the failure with the log. Cause: A migration step raised inside the transaction; the transaction was rolled back. Resolution: File a bug with the log; restore the backup if the database is unusable. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `from` | `integer` | yes | — | | `to` | `integer` | yes | — | | `name` | `string` | yes | — | | `backup_path` | `string` | yes | — | ### `DB_CORRUPT` | Property | Value | |---|---| | Title | Database corrupt | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `The database {path} is corrupt; it was moved to {quarantine_path}.` Hint: Restore a backup with 'browserhive db restore ' or start fresh. Cause: `PRAGMA quick_check` failed or SQLite reported corruption. Resolution: Restore the most recent backup from `backups/`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `path` | `string` | yes | — | | `quarantine_path` | `string` | yes | — | ### `UNHANDLED` | Property | Value | |---|---| | Title | Unhandled error | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `Unhandled {kind} reached the process handler: {name}.` Hint: Check the log for the stack; the server keeps running when the failure is contained. Cause: A promise rejected with no handler or an exception escaped every boundary; the CLI process handlers record it as a degradation (spec 10 §3). Resolution: Report the log line; repeated occurrences on `/system.degradations` indicate a bug in a background task. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `kind` | one of `exception`, `rejection` | yes | — | | `name` | `string` | yes | — | ### `RETENTION_FAILED` | Property | Value | |---|---| | Title | Retention sweep failed | | HTTP status | 500 | | Category | `boot` | | Retryable | `backoff` (retry later with backoff) | | Exit code | 1 | Message: `Retention step {step} failed: {reason}` Hint: The sweep retries on the next interval; check disk space and file permissions. Cause: A step of the periodic retention sweep (row pruning, byte cap, vacuum or artifact enqueue) threw. Recorded as a degradation, never fatal. Resolution: Fix the underlying cause (disk, permissions, locks); the degradation resolves on the next clean pass. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `step` | `string` | yes | — | | `reason` | `string` | yes | — | ### `STALE_BROWSER_PROCESSES` | Property | Value | |---|---| | Title | Stale browser processes found | | HTTP status | 500 | | Category | `boot` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | | Exit code | 1 | Message: `{count} browser process(es) from a previous run are still alive.` Hint: Stop them manually if they hold profile locks or memory. Cause: Chromium processes launched by an earlier BrowserHive survived its exit (crash or SIGKILL). Recorded as a boot-time degradation, never fatal. Resolution: Kill the listed processes; they are not adopted by the new server. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `count` | `integer` | yes | — | ## Authentication errors Returned by the REST API, `/mcp` and the WebSocket handshake. | Code | Title | HTTP | Retryable | |---|---|---|---| | [`UNAUTHORIZED`](#UNAUTHORIZED) | Unauthorized | 401 | never | | [`INVALID_CREDENTIALS`](#INVALID_CREDENTIALS) | Invalid credentials | 401 | never | | [`FORBIDDEN`](#FORBIDDEN) | Forbidden | 403 | never | | [`PASSWORD_CHANGE_REQUIRED`](#PASSWORD_CHANGE_REQUIRED) | Password change required | 403 | after_operator | | [`BAD_CURRENT_PASSWORD`](#BAD_CURRENT_PASSWORD) | Current password incorrect | 400 | different_args | | [`WEAK_PASSWORD`](#WEAK_PASSWORD) | Password too weak | 400 | different_args | ### `UNAUTHORIZED` | Property | Value | |---|---| | Title | Unauthorized | | HTTP status | 401 | | Category | `auth` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Authentication required.` Hint: Log in, or send a valid bearer token. Cause: No credential was presented, or the presented one is invalid or expired. Resolution: Authenticate with the dashboard session cookie or a bearer token. Details: none. ### `INVALID_CREDENTIALS` | Property | Value | |---|---| | Title | Invalid credentials | | HTTP status | 401 | | Category | `auth` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Invalid username or password.` Hint: Check the password; reset it with browserhive admin reset-password. Cause: The login password did not match, or the account is locked out. Resolution: Retry with the correct password or reset it from the CLI. Details: none. ### `FORBIDDEN` | Property | Value | |---|---| | Title | Forbidden | | HTTP status | 403 | | Category | `auth` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `This action requires the '{scope}' scope.` Hint: Use a principal that holds the scope. Cause: The principal lacks the scope the route or tool declares. Resolution: Operators hold every scope; issue an operator token with the needed scope. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `scope` | `string` | yes | — | ### `PASSWORD_CHANGE_REQUIRED` | Property | Value | |---|---| | Title | Password change required | | HTTP status | 403 | | Category | `auth` | | Retryable | `after_operator` (retry after an operator acts (unlock the vault, resolve a request, change policy)) | Message: `The seed password must be changed before continuing.` Hint: Change the password on the login screen. Cause: The operator account still uses the generated seed password. Resolution: Set a new password; the seed file is shredded afterwards. Details: none. ### `BAD_CURRENT_PASSWORD` | Property | Value | |---|---| | Title | Current password incorrect | | HTTP status | 400 | | Category | `auth` | | Retryable | `different_args` (retry only with different arguments) | Message: `The current password is incorrect.` Hint: Re-enter the current password. Cause: The password-change request carried a wrong current password. Resolution: Retry with the correct current password. Details: none. ### `WEAK_PASSWORD` | Property | Value | |---|---| | Title | Password too weak | | HTTP status | 400 | | Category | `auth` | | Retryable | `different_args` (retry only with different arguments) | Message: `The new password must be at least {min_length} characters long.` Hint: Choose a longer password. Cause: The new password is shorter than the minimum length. Resolution: Use at least the minimum number of characters. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `min_length` | `integer` | yes | — | ## Transport errors Protocol-level failures of HTTP and WebSocket requests (validation, limits, conflicts). | Code | Title | HTTP | Retryable | |---|---|---|---| | [`ORIGIN_NOT_ALLOWED`](#ORIGIN_NOT_ALLOWED) | Origin not allowed | 403 | never | | [`HOST_NOT_ALLOWED`](#HOST_NOT_ALLOWED) | Host not allowed | 421 | never | | [`RATE_LIMITED`](#RATE_LIMITED) | Rate limited | 429 | backoff | | [`PAYLOAD_TOO_LARGE`](#PAYLOAD_TOO_LARGE) | Payload too large | 413 | different_args | | [`VALIDATION_FAILED`](#VALIDATION_FAILED) | Validation failed | 400 | different_args | | [`NOT_FOUND`](#NOT_FOUND) | Not found | 404 | never | | [`METHOD_NOT_ALLOWED`](#METHOD_NOT_ALLOWED) | Method not allowed | 405 | never | | [`CONFLICT`](#CONFLICT) | Conflict | 409 | different_args | | [`NOT_ACCEPTABLE`](#NOT_ACCEPTABLE) | Not acceptable | 406 | different_args | | [`WS_PROTOCOL_ERROR`](#WS_PROTOCOL_ERROR) | WebSocket protocol error | 400 | never | | [`WS_OVERLOADED`](#WS_OVERLOADED) | WebSocket overloaded | 503 | backoff | ### `ORIGIN_NOT_ALLOWED` | Property | Value | |---|---| | Title | Origin not allowed | | HTTP status | 403 | | Category | `transport` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The request Origin is not allowed.` Hint: Open the dashboard from the server URL itself. Cause: A browser request carried an `Origin` that does not match the server (CSRF guard). Resolution: Use the dashboard served by this BrowserHive; no cross-origin access is allowed. Details: none. ### `HOST_NOT_ALLOWED` | Property | Value | |---|---| | Title | Host not allowed | | HTTP status | 421 | | Category | `transport` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The request Host header is not allowed.` Hint: Address the server by the host it is bound to. Cause: DNS-rebinding guard: the `Host` header does not match the bound host. Resolution: Use the bound host or IP in the URL. Details: none. ### `RATE_LIMITED` | Property | Value | |---|---| | Title | Rate limited | | HTTP status | 429 | | Category | `transport` | | Retryable | `backoff` (retry later with backoff) | Message: `Too many requests. Retry after {retry_after_ms} ms.` Hint: Back off for the given interval. Cause: The client exceeded the login or API rate limit. Resolution: Wait for `retry_after_ms` and retry. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `retry_after_ms` | `integer` | yes | — | ### `PAYLOAD_TOO_LARGE` | Property | Value | |---|---| | Title | Payload too large | | HTTP status | 413 | | Category | `transport` | | Retryable | `different_args` (retry only with different arguments) | Message: `The request body exceeds the limit of {limit_bytes} bytes.` Hint: Send a smaller body. Cause: The body is larger than the route’s limit. Resolution: Reduce the payload size. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `limit_bytes` | `integer` | yes | — | ### `VALIDATION_FAILED` | Property | Value | |---|---| | Title | Validation failed | | HTTP status | 400 | | Category | `transport` | | Retryable | `different_args` (retry only with different arguments) | Message: `The request failed validation.` Hint: Each issue names the field and what is wrong. Cause: Params, query or body did not match the route schema; unknown keys are errors. Resolution: Fix the listed fields. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `issues` | `object[]` | yes | each item: keys `path`, `message`, `code` | ### `NOT_FOUND` | Property | Value | |---|---| | Title | Not found | | HTTP status | 404 | | Category | `transport` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The requested resource does not exist.` Hint: Check the path and id. Cause: No route or resource matches the request. Resolution: Consult /api/v1/docs for the route table. Details: none. ### `METHOD_NOT_ALLOWED` | Property | Value | |---|---| | Title | Method not allowed | | HTTP status | 405 | | Category | `transport` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The method is not allowed for this resource.` Hint: Use one of the allowed methods. Cause: The route exists but not for this HTTP method. Resolution: See `details.allow` and the `Allow` header. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `allow` | `string[]` | yes | — | ### `CONFLICT` | Property | Value | |---|---| | Title | Conflict | | HTTP status | 409 | | Category | `transport` | | Retryable | `different_args` (retry only with different arguments) | Message: `The resource changed since you last read it.` Hint: Reload the resource and retry with the current version. Cause: An optimistic-concurrency check (`If-Match`/version) failed; the HTTP layer may use 412. Resolution: Re-read, then resubmit with `current_version`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `current_version` | `integer` | no | — | ### `NOT_ACCEPTABLE` | Property | Value | |---|---| | Title | Not acceptable | | HTTP status | 406 | | Category | `transport` | | Retryable | `different_args` (retry only with different arguments) | Message: `None of the requested media types is supported.` Hint: Ask for one of the supported types. Cause: The `Accept` header lists no representation the route can produce. Resolution: See `details.supported`. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `supported` | `string[]` | yes | — | ### `WS_PROTOCOL_ERROR` | Property | Value | |---|---| | Title | WebSocket protocol error | | HTTP status | 400 | | Category | `transport` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The WebSocket client violated the protocol.` Hint: Reconnect with a compliant client. Cause: Malformed envelope, unknown command, wrong protocol version, or a command before subscribe. Resolution: Fix the client; the socket is closed with the code in spec 03 §6.4. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `violations` | `string[]` | yes | — | ### `WS_OVERLOADED` | Property | Value | |---|---| | Title | WebSocket overloaded | | HTTP status | 503 | | Category | `transport` | | Retryable | `backoff` (retry later with backoff) | Message: `The connection fell too far behind ({buffered_bytes} bytes buffered).` Hint: Reconnect and resubscribe with the last cursor. Cause: Backpressure: the client did not drain the feed within the buffer bounds. Resolution: Reconnect; the feed replays from your cursor or asks for a resync. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `buffered_bytes` | `integer` | yes | — | ## Audit outcomes Recorded in the audit trail (vault log, attention history). Some are also returned inside a tool result. | Code | Title | HTTP | Retryable | |---|---|---|---| | [`ORIGIN_MISMATCH`](#ORIGIN_MISMATCH) | Origin not on the allow-list | 403 | different_args | | [`VAULT_FILL_AUTH_FAILED`](#VAULT_FILL_AUTH_FAILED) | Vault fill: authentication failed | 500 | never | | [`VAULT_FILL_BLOCKED`](#VAULT_FILL_BLOCKED) | Vault fill: blocked | 500 | never | | [`VAULT_LIST_DENIED`](#VAULT_LIST_DENIED) | Vault list: denied | 500 | never | | [`ATTENTION_REJECTED`](#ATTENTION_REJECTED) | Attention: rejected | 500 | never | | [`ATTENTION_TIMEOUT`](#ATTENTION_TIMEOUT) | Attention: timed out | 500 | never | | [`ATTENTION_CANCELLED`](#ATTENTION_CANCELLED) | Attention: cancelled | 500 | never | ### `ORIGIN_MISMATCH` | Property | Value | |---|---| | Title | Origin not on the allow-list | | HTTP status | 403 | | Category | `audit` | | Retryable | `different_args` (retry only with different arguments) | Message: `Origin '{page_origin}' is not on the allow-list for vault entry '{entry_name}'.` Hint: Navigate to an allowed origin before filling. Cause: The page origin at fill time did not match the binding’s allowed origins. Resolution: Fill only on the sites the operator bound the entry to. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `page_origin` | `string` | yes | — | | `entry_name` | `string` | yes | — | | `allowed` | `string[]` | yes | — | ### `VAULT_FILL_AUTH_FAILED` | Property | Value | |---|---| | Title | Vault fill: authentication failed | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The vault fill completed but the site did not accept the credentials.` Hint: Classification of a returned vault_fill status; not an exception. Cause: `vault_fill` returned `auth_failed`. Resolution: Check the credential in the vault; nothing to retry automatically. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `entry_name` | `string` | yes | — | ### `VAULT_FILL_BLOCKED` | Property | Value | |---|---| | Title | Vault fill: blocked | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The vault fill was blocked by policy.` Hint: Classification of a returned vault_fill status; not an exception. Cause: `vault_fill` returned `blocked` (policy gate refused). Resolution: See the reason; operators adjust bindings and policies. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `entry_name` | `string` | yes | — | | `reason` | `string` | no | — | ### `VAULT_LIST_DENIED` | Property | Value | |---|---| | Title | Vault list: denied | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The vault listing was denied for this session.` Hint: Classification of a returned vault_list_available status; not an exception. Cause: `vault_list_available` returned no entries because policy denied the session. Resolution: Operators adjust bindings and policies. Details: none. ### `ATTENTION_REJECTED` | Property | Value | |---|---| | Title | Attention: rejected | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `The attention request was rejected.` Hint: Classification of a returned request_attention status; not an exception. Cause: The operator rejected the request, or the session closed while it was open. Resolution: Read the returned message; continue or stop the task accordingly. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | ### `ATTENTION_TIMEOUT` | Property | Value | |---|---| | Title | Attention: timed out | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Attention request timed out; the operator was not available to respond.` Hint: Classification of a returned request_attention status; not an exception. Cause: Nobody resolved the request before its deadline. Resolution: Retry later with a longer wait, or proceed without the operator. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | ### `ATTENTION_CANCELLED` | Property | Value | |---|---| | Title | Attention: cancelled | | HTTP status | 500 | | Category | `audit` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `Client cancelled the attention request.` Hint: Classification of a returned request_attention status; not an exception. Cause: The MCP client disconnected or sent notifications/cancelled. Resolution: Nothing to do. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `request_id` | `string` | yes | — | ## Warnings Non-fatal degradations recorded on a session or in the system events list; the operation continues. | Code | Title | HTTP | Retryable | |---|---|---|---| | [`EXECUTABLE_PATH_OVERRIDE`](#EXECUTABLE_PATH_OVERRIDE) | Executable path override | 500 | never | | [`TRACE_START_FAILED`](#TRACE_START_FAILED) | Trace could not start | 500 | never | | [`TRACE_FINALIZE_FAILED`](#TRACE_FINALIZE_FAILED) | Trace could not be finalized | 500 | never | | [`STEALTH_INIT_FAILED`](#STEALTH_INIT_FAILED) | Stealth init failed | 500 | never | | [`BLOCKLIST_ROUTE_FAILED`](#BLOCKLIST_ROUTE_FAILED) | Blocklist route failed | 500 | never | | [`BYO_PROXY_UNSEEDED`](#BYO_PROXY_UNSEEDED) | BYO proxy: geo not seeded | 500 | never | | [`VIEWPORT_OVERRIDE_UNASSERTED`](#VIEWPORT_OVERRIDE_UNASSERTED) | Viewport override: display not asserted | 500 | never | | [`IDENTITY_SEED_SAVE_FAILED`](#IDENTITY_SEED_SAVE_FAILED) | Identity seed not saved | 500 | never | | [`REAP_DEAD_FAILED`](#REAP_DEAD_FAILED) | Dead session reap failed | 500 | never | | [`CDP_SESSION_LEAKED`](#CDP_SESSION_LEAKED) | CDP session leaked | 500 | never | | [`SCREENSHOT_ARCHIVE_FAILED`](#SCREENSHOT_ARCHIVE_FAILED) | Screenshot archive failed | 500 | never | ### `EXECUTABLE_PATH_OVERRIDE` | Property | Value | |---|---| | Title | Executable path override | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: A launch option set `executablePath`, which disables channel routing. Resolution: Prefer `channel`; use `executablePath` only for custom builds. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `TRACE_START_FAILED` | Property | Value | |---|---| | Title | Trace could not start | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: Playwright tracing failed to start for the session; the session continues without a trace. Resolution: Check disk space and the session directory permissions. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `TRACE_FINALIZE_FAILED` | Property | Value | |---|---| | Title | Trace could not be finalized | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: Stopping the trace failed or exceeded the 10 s cap at close. Resolution: The trace may be incomplete; check disk space. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `STEALTH_INIT_FAILED` | Property | Value | |---|---| | Title | Stealth init failed | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: The CDP identity override could not be applied; the session runs with reduced stealth. Resolution: Check the driver (Patchright/Playwright) version compatibility. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `BLOCKLIST_ROUTE_FAILED` | Property | Value | |---|---| | Title | Blocklist route failed | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: The network-level blocklist route could not be installed; tool-level enforcement still applies. Resolution: Check the driver version; report if it repeats. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `BYO_PROXY_UNSEEDED` | Property | Value | |---|---| | Title | BYO proxy: geo not seeded | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: A caller-supplied proxy suppresses geo-derived identity because the exit location is unknown. Resolution: Pass `locale`/`timezoneId` explicitly when using your own proxy. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `VIEWPORT_OVERRIDE_UNASSERTED` | Property | Value | |---|---| | Title | Viewport override: display not asserted | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: A caller-supplied viewport disables the display-coherence assertion of the fingerprint. Resolution: Omit `viewport` to let the identity pick a coherent display. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `IDENTITY_SEED_SAVE_FAILED` | Property | Value | |---|---| | Title | Identity seed not saved | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: The `.identity.json` sidecar could not be written with the full profile. Resolution: Check the auth-states directory permissions. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `REAP_DEAD_FAILED` | Property | Value | |---|---| | Title | Dead session reap failed | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: Closing a dead session raised; the sweeper will retry. Resolution: No action; report if it repeats for the same session. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `CDP_SESSION_LEAKED` | Property | Value | |---|---| | Title | CDP session leaked | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: A CDP session was still attached when its page closed and had to be pruned late. Resolution: No action; report if it repeats. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | ### `SCREENSHOT_ARCHIVE_FAILED` | Property | Value | |---|---| | Title | Screenshot archive failed | | HTTP status | 500 | | Category | `warning` | | Retryable | `never` (do not retry; the request cannot succeed as sent) | Message: `{message}` Hint: Session warning: logged and broadcast on session:, never thrown. Cause: A tool screenshot could not be written to the session directory. Resolution: Check disk space and permissions. Details: | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `object` | no | additional keys allowed | --- # REST API reference Source: https://browserhive.ai/docs/reference/api/ The admin REST API served under `/api/v1` on the same port as MCP and the dashboard when `--admin` is on (84 operations), generated from `HTTP_ENDPOINTS` in `@browserhive/contracts/http`. Request and response schemas are in the OpenAPI 3.1 document the server serves at `/api/v1/openapi.json`, with an interactive reference UI at `/api/v1/docs`. Summaries come from `packages/contracts/generated/openapi.json`. ## Conventions - **Authentication:** `cookie` is the dashboard session cookie `browserhive_session` (from `POST /api/v1/auth/login`); `bearer` is `Authorization: Bearer ` (operator API tokens or agent tokens); `grant` is a short-lived `?grant=` accepted only on trace and screenshot downloads; `public` needs nothing. - **Authorization:** the scope column is checked for the caller. Operators hold every scope; agent tokens hold `mcp:tools` only. - **Wire format:** JSON bodies are snake_case; timestamps are epoch milliseconds. - **Errors:** `application/problem+json` with a registry `code`; see the [error reference](/docs/reference/errors/). - **Collections:** `{ data, page: { next_cursor, prev_cursor?, limit, total? }, facets?, applied, meta }`, cursor-paginated; unknown query keys are rejected with 400. - **Concurrency:** vault bindings and group policies take `If-Match: `; a stale version returns 409 `CONFLICT`. Bulk operations accept `Idempotency-Key`. - **Realtime:** `GET /api/v1/ws` upgrades to the WebSocket protocol described in the [WebSocket reference](/docs/reference/websocket/). ## Scopes `sessions:read` · `sessions:write` · `sessions:takeover` · `attention:read` · `attention:resolve` · `vault:read` · `vault:write` · `vault:confirm` · `blocklist:read` · `blocklist:write` · `system:read` · `system:write` · `logs:read` · `notifications:read` · `notifications:write` · `preferences:write` · `mcp:tools` ## Health | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/health` | `getHealth` | — | public | Liveness/readiness; 200 only when ready (also served at `/health`). | ## Authentication | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | POST | `/api/v1/auth/login` | `login` | — | public | Log in with the operator password; sets the session cookie. | | POST | `/api/v1/auth/logout` | `logout` | — | cookie, bearer | Destroy the current session and clear the cookie. | | GET | `/api/v1/auth/me` | `getMe` | — | cookie, bearer | The authenticated principal. | | POST | `/api/v1/auth/change-password` | `changePassword` | — | cookie, bearer | Change the operator password; revokes every other session. | | GET | `/api/v1/auth/sessions` | `listAuthSessions` | — | cookie, bearer | The caller's operator sessions. | | DELETE | `/api/v1/auth/sessions/{id_prefix}` | `revokeAuthSession` | — | cookie, bearer | Revoke one operator session by id prefix. | | POST | `/api/v1/auth/sessions/revoke-all` | `revokeAllAuthSessions` | — | cookie, bearer | Revoke every session of the caller except the current one. | | GET | `/api/v1/auth/tokens` | `listTokens` | — | cookie, bearer | Issued API tokens (never the secret). | | POST | `/api/v1/auth/tokens` | `createToken` | — | cookie, bearer | Issue an API token; the token is shown once. | | DELETE | `/api/v1/auth/tokens/{credential_id}` | `revokeToken` | — | cookie, bearer | Revoke an API token. | | POST | `/api/v1/auth/grants` | `createGrant` | — | cookie, bearer | Mint a single-use, 10-minute grant: `trace` → resource_id is the session id, `screenshot` → the event id. | ## Sessions | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/sessions` | `listSessions` | `sessions:read` | cookie, bearer | List sessions with facets; the live registry overlays stored rows. | | POST | `/api/v1/sessions/bulk` | `bulkSessions` | `sessions:write` | cookie, bearer | Archive, unarchive, terminate or delete up to 100 sessions (per-item results). | | GET | `/api/v1/sessions/{session_id}` | `getSession` | `sessions:read` | cookie, bearer | One session with trace/data-dir descriptors and counters (no embedded arrays). | | GET | `/api/v1/sessions/{session_id}/tool-calls` | `listSessionToolCalls` | `sessions:read` | cookie, bearer | Tool calls of one session (`?expand=detail` adds args/result). | | GET | `/api/v1/sessions/{session_id}/tool-calls/{event_id}` | `getSessionToolCall` | `sessions:read` | cookie, bearer | One tool call with args, result and its screenshot. | | GET | `/api/v1/sessions/{session_id}/pages` | `listSessionPages` | `sessions:read` | cookie, bearer | Pages visited by one session. | | GET | `/api/v1/sessions/{session_id}/attention` | `listSessionAttention` | `attention:read` | cookie, bearer | Attention requests of one session. | | GET | `/api/v1/sessions/{session_id}/vault-access` | `listSessionVaultAccess` | `vault:read` | cookie, bearer | Vault access audit rows of one session. | | GET | `/api/v1/sessions/{session_id}/blocked` | `listSessionBlocked` | `blocklist:read` | cookie, bearer | Blocked requests of one session. | | GET | `/api/v1/sessions/{session_id}/screenshots` | `listSessionScreenshots` | `sessions:read` | cookie, bearer | Screenshots of one session (image URLs accept grants). | | GET | `/api/v1/sessions/{session_id}/timeline` | `getSessionTimeline` | `sessions:read` | cookie, bearer | Merged timeline of tool calls, pages, attention, vault and blocked rows. | | GET | `/api/v1/sessions/{session_id}/screenshots/{event_id}` | `getScreenshotImage` | `sessions:read` | cookie, bearer, grant | Screenshot bytes (cookie, bearer or `?grant=` for route `screenshot` = event id). | | GET | `/api/v1/sessions/{session_id}/trace.zip` | `getTraceZip` | `sessions:read` | cookie, bearer, grant | The session trace (single `Range` supported; `?grant=` for route `trace` = session id). | | HEAD | `/api/v1/sessions/{session_id}/trace.zip` | `headTraceZip` | `sessions:read` | cookie, bearer, grant | Trace size probe. | | GET | `/api/v1/sessions/{session_id}/trace` | `getSessionTrace` | `sessions:read` | cookie, bearer | Trace descriptor. `viewer_url` embeds the trace.zip URL; the client appends `?grant=` to that inner URL. | | POST | `/api/v1/sessions/{session_id}/data-dir/reveal` | `revealSessionDataDir` | `sessions:read` | cookie, bearer | Open the session's data directory in the host file manager (honest result). | | POST | `/api/v1/sessions/{session_id}/terminate` | `terminateSession` | `sessions:write` | cookie, bearer | Close a live session (operator reason). | | POST | `/api/v1/sessions/{session_id}/archive` | `archiveSession` | `sessions:write` | cookie, bearer | Archive a finished session (exempt from retention). | | POST | `/api/v1/sessions/{session_id}/unarchive` | `unarchiveSession` | `sessions:write` | cookie, bearer | Unarchive a session. | | DELETE | `/api/v1/sessions/{session_id}` | `deleteSession` | `sessions:write` | cookie, bearer | Terminate if live, then delete rows and artifacts. | | POST | `/api/v1/sessions/{session_id}/viewport` | `setSessionViewport` | `sessions:write` | cookie, bearer | Resize the active page viewport; not attention-gated (D-10). | | POST | `/api/v1/sessions/{session_id}/input` | `sendSessionInput` | `sessions:takeover` | cookie, bearer | Operator takeover input; each input re-checks the open takeover attention request (per-item results). | | GET | `/api/v1/sessions/{session_id}/export` | `exportSession` | `sessions:read` | cookie, bearer | Streamed timeline export (NDJSON or CSV by `Accept`), capped at 100k rows. | ## Activity and metrics | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/tool-calls` | `listToolCalls` | `sessions:read` | cookie, bearer | Tool calls across sessions (live feed seed, fleet error views). | | GET | `/api/v1/activity` | `getActivity` | `sessions:read` | cookie, bearer | Gap-filled activity buckets (≤ 720) and headline counters. | | GET | `/api/v1/metrics/tools` | `getToolMetrics` | `sessions:read` | cookie, bearer | Per-tool call counts, error rate and latency percentiles. | ## Websites (pages) | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/pages` | `listPages` | `sessions:read` | cookie, bearer | Pages across sessions (navigation history) with category facets. | | GET | `/api/v1/pages/recent` | `listRecentPages` | `sessions:read` | cookie, bearer | Most recent page visits across sessions. | | GET | `/api/v1/pages/domains` | `listPageDomains` | `sessions:read` | cookie, bearer | Most visited domains (all-time when no window). | ## Attention | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/attention` | `listAttention` | `attention:read` | cookie, bearer | Attention requests (open and history) with the live open count and status/mode facets. | | POST | `/api/v1/attention/{request_id}/resolve` | `resolveAttention` | `attention:resolve` | cookie, bearer | Resolve or reject an open attention request. | | POST | `/api/v1/attention/bulk` | `bulkAttention` | `attention:resolve` | cookie, bearer | Resolve or reject several attention requests (per-item results). | ## Vault | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/vault/confirm` | `listVaultConfirm` | `vault:read` | cookie, bearer | Vault fill confirmations (open and history). | | POST | `/api/v1/vault/confirm/{request_id}/resolve` | `resolveVaultConfirm` | `vault:confirm` | cookie, bearer | Approve or deny a pending vault fill (`reason` is audit-only). | | POST | `/api/v1/vault/confirm/bulk` | `bulkVaultConfirm` | `vault:confirm` | cookie, bearer | Approve or deny several vault confirmations (per-item results). | | GET | `/api/v1/vault` | `getVault` | `vault:read` | cookie, bearer | Backend capabilities, unlock descriptor and counts (never shells out). | | GET | `/api/v1/vault/status` | `getVaultStatus` | `vault:read` | cookie, bearer | Lock state (may call the backend). | | POST | `/api/v1/vault/unlock` | `unlockVault` | `vault:write` | cookie, bearer | Unlock with the secret `unlock.mode` names (Bitwarden: a session token, never the master password). | | POST | `/api/v1/vault/lock` | `lockVault` | `vault:write` | cookie, bearer | Forget the backend session. | | POST | `/api/v1/vault/sync` | `syncVault` | `vault:write` | cookie, bearer | Refresh the backend's local cache. | | GET | `/api/v1/vault/groups` | `listVaultGroups` | `vault:read` | cookie, bearer | Backend groups with item/binding coverage, policies and same-name duplicates. | | PUT | `/api/v1/vault/groups/{group_id}/policy` | `putVaultGroupPolicy` | `vault:write` | cookie, bearer | Create or update a group policy (`If-Match: ` on update). | | GET | `/api/v1/vault/items` | `listVaultItems` | `vault:read` | cookie, bearer | Backend items with derived handles and binding coverage. | | GET | `/api/v1/vault/bindings` | `listVaultBindings` | `vault:read` | cookie, bearer | Stored bindings, ordered by handle. | | PUT | `/api/v1/vault/bindings/{handle}` | `putVaultBinding` | `vault:write` | cookie, bearer | Create (item_name required) or update a binding (`If-Match: `). | | DELETE | `/api/v1/vault/bindings/{handle}` | `deleteVaultBinding` | `vault:write` | cookie, bearer | Remove a binding. | | POST | `/api/v1/vault/bindings/resolve` | `resolveVaultBindings` | `vault:read` | cookie, bearer | Dry-run the fill gates of every binding against a URL. | | GET | `/api/v1/vault/log` | `listVaultLog` | `vault:read` | cookie, bearer | Vault access audit log. | | GET | `/api/v1/vault/export` | `exportVault` | `vault:read` | cookie, bearer | Export bindings and policies as the v3 document. | | POST | `/api/v1/vault/import` | `importVault` | `vault:write` | cookie, bearer | Import a v3 document (`?mode=merge\|replace`). | ## Blocklist | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/blocklist` | `getBlocklist` | `blocklist:read` | cookie, bearer | Loaded patterns with hit counts, skipped lines and window stats. | | POST | `/api/v1/blocklist/reload` | `reloadBlocklist` | `blocklist:write` | cookie, bearer | Re-read the blocklist file; on failure the previous list stays active. | | GET | `/api/v1/blocklist/attempts` | `listBlockedAttempts` | `blocklist:read` | cookie, bearer | Blocked request audit (served even when no blocklist is configured). | ## System and configuration | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/system` | `getSystem` | `system:read` | cookie, bearer | Server facts, runtime, capacity, retention, storage, telemetry and open degradations. | | GET | `/api/v1/system/config` | `getSystemConfig` | `system:read` | cookie, bearer | Every config key with its value, source and shadowed values (secrets redacted). | | GET | `/api/v1/system/realtime` | `getSystemRealtime` | `system:read` | cookie, bearer | Open realtime connections with topics, screencasts and backpressure counters. | | PATCH | `/api/v1/system/log-level` | `setLogLevel` | `system:write` | cookie, bearer | Change the log level spec at runtime (`info,sessions=debug`). | | GET | `/api/v1/system/events` | `listSystemEvents` | `system:read` | cookie, bearer | Degradations (`resolved=open` by default). | ## Logs | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/logs` | `listLogs` | `logs:read` | cookie, bearer | Records from the in-process ring buffer: newest first by default (`dir=desc`, the cursor pages to older records); `dir=asc` pages oldest to newest; `after_seq` bounds to newer records. | | GET | `/api/v1/logs/export` | `exportLogs` | `logs:read` | cookie, bearer | Every matching ring-buffer record as NDJSON. | ## API description | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/openapi.json` | `getOpenApi` | — | public | This OpenAPI 3.1 document. | | GET | `/api/v1/docs` | `getDocs` | — | public | API reference UI (admin surface only). | ## Notifications and preferences | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/notifications` | `listNotifications` | `notifications:read` | cookie, bearer | Notifications newest first with the unread count. | | POST | `/api/v1/notifications/{notification_id}/read` | `markNotificationRead` | `notifications:write` | cookie, bearer | Mark one notification read. | | POST | `/api/v1/notifications/read-all` | `markAllNotificationsRead` | `notifications:write` | cookie, bearer | Mark every notification read. | | DELETE | `/api/v1/notifications/{notification_id}` | `dismissNotification` | `notifications:write` | cookie, bearer | Dismiss one notification. | | POST | `/api/v1/notifications/dismiss-all` | `dismissAllNotifications` | `notifications:write` | cookie, bearer | Dismiss every notification. | | GET | `/api/v1/me/preferences` | `getPreferences` | — | cookie, bearer | The caller's stored preferences (known keys only). | | PUT | `/api/v1/me/preferences` | `putPreferences` | `preferences:write` | cookie, bearer | Replace the preferences document (≤ 64 KiB; unknown keys rejected). | ## Search | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/search` | `search` | `sessions:read` | cookie, bearer | Entity search for the command palette. | ## Client errors | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | POST | `/api/v1/client-errors` | `reportClientError` | — | cookie, bearer | Record an uncaught dashboard error (rate-limited 30/min). | ## Realtime | Method | Path | operationId | Scope | Auth | Summary | |---|---|---|---|---|---| | GET | `/api/v1/ws` | `wsUpgrade` | — | cookie, bearer | Realtime WebSocket (`Sec-WebSocket-Protocol: browserhive.v1`). Auth failures upgrade then close 4401; see the WS protocol. | --- # WebSocket reference Source: https://browserhive.ai/docs/reference/websocket/ Realtime protocol v1 used by the dashboard for live feeds, the screencast and takeover input. Generated from `@browserhive/contracts/ws`. Available when `--admin` is on. ## Handshake - URL: `ws://:/api/v1/ws` (same port as everything else). - Subprotocol: `Sec-WebSocket-Protocol: browserhive.v1`. - Authentication: the dashboard session cookie or `Authorization: Bearer `, checked at upgrade. A failed check completes the upgrade and closes immediately with `4401`, so browsers see the code. - The first server frame is a `reply` with payload `type: "hello"`, carrying `epoch` and the current `cursor`. `epoch` changes on every server start; a cursor from another epoch cannot be replayed. ## Envelope Every server text frame is JSON: ```ts { v: 1, kind: "event" | "reply" | "error" | "stream", seq: number, ts: number, topic?: string, corr?: string, payload: unknown } ``` | kind | Meaning | |---|---| | `event` | ordered, replayable feed event on a topic (has `topic` and `seq`) | | `reply` | answer to a client command (echoes `corr`); also the first `hello` frame | | `error` | command failure or protocol violation; the socket stays open | | `stream` | screencast control message on `screencast:`; latest-wins, never replayed | Client frames are JSON objects discriminated on `type` (see [client commands](#client-commands)); any command may carry `corr` (1–64 characters), which is echoed on its reply or error. ## Limits | Limit | Value | |---|---| | `maxInboundFrameBytes` | 16KiB | | `maxProtocolViolations` | 5 | | `feedBufferCount` | 10000 | | `feedBufferBytes` | 8MiB | | `feedBufferMs` | 5m | | `heartbeatMs` | 20s | | `staleMs` | 1m | | `tickMs` | 30s | | `overloadBytes` | 4MiB | | `overloadGraceMs` | 10s | | `screencastDropBytes` | 1MiB | ## Close codes | Code | Name | Meaning | |---|---|---| | 4401 | `UNAUTHORIZED` | not authenticated or the session expired; log in again | | 4403 | `PASSWORD_CHANGE_REQUIRED` | the operator must change the password first | | 4400 | `PROTOCOL_ERROR` | too many malformed frames | | 4406 | `BAD_SUBPROTOCOL` | missing or unknown `Sec-WebSocket-Protocol` (expected `browserhive.v1`) | | 1013 | `OVERLOADED` | the client did not read fast enough (backpressure limit exceeded) | | 1001 | `GOING_AWAY` | server shutting down, or the socket was silent too long | ## Client commands | type | Fields | Scope | |---|---|---| | `ping` | — | any authenticated caller | | `subscribe` | `topic`: `string`, `cursor?`: `integer` | any authenticated caller | | `unsubscribe` | `topic`: `string` | any authenticated caller | | `screencast.start` | `session_id`: `string`, `max_width?`: `integer`, `max_height?`: `integer`, `quality?`: `integer` | `sessions:read` | | `screencast.stop` | `session_id`: `string` | `sessions:read` | | `screencast.set_size` | `session_id`: `string`, `max_width`: `integer`, `max_height`: `integer` | `sessions:read` | | `input` | `session_id`: `string`, `input`: `object` | `sessions:takeover` | | `session.set_viewport` | `session_id`: `string`, `width`: `integer`, `height`: `integer` | `sessions:write` | | `logs.tail` | `level?`: one of `error`, `warn`, `info`, `debug`, `trace`, `module?`: `string` | `logs:read` | `input` is accepted only while an attention request is open for the session and is re-checked on every message (`INPUT_NOT_PERMITTED` otherwise). `session.set_viewport` is an observability control and is not attention-gated. ### Takeover input (`LiveInput`) Field names follow the Chrome DevTools Protocol (camelCase). `modifiers` is a bitmask: Alt=1, Ctrl=2, Meta=4, Shift=8. #### `mouse` | Field | Type | Required | Constraints | |---|---|---|---| | `type` | `"mouse"` | yes | — | | `action` | one of `mouseMoved`, `mousePressed`, `mouseReleased`, `mouseWheel` | yes | — | | `x` | `number` | yes | ≥ 0; ≤ 100000 | | `y` | `number` | yes | ≥ 0; ≤ 100000 | | `button` | one of `none`, `left`, `middle`, `right` | no | — | | `clickCount` | `integer` | no | ≥ 0; ≤ 8 | | `deltaX` | `number` | no | ≥ -10000; ≤ 10000 | | `deltaY` | `number` | no | ≥ -10000; ≤ 10000 | | `modifiers` | `integer` | no | ≥ 0; ≤ 15 | #### `key` | Field | Type | Required | Constraints | |---|---|---|---| | `type` | `"key"` | yes | — | | `action` | one of `keyDown`, `keyUp`, `char`, `rawKeyDown` | yes | — | | `key` | `string` | no | max length 32 | | `code` | `string` | no | max length 64 | | `text` | `string` | no | max length 16 | | `windowsVirtualKeyCode` | `integer` | no | ≥ 0; ≤ 255 | | `modifiers` | `integer` | no | ≥ 0; ≤ 15 | #### `touch` | Field | Type | Required | Constraints | |---|---|---|---| | `type` | `"touch"` | yes | — | | `action` | one of `touchStart`, `touchEnd`, `touchMove`, `touchCancel` | yes | — | | `points` | `object[]` | yes | at most 10 items; each item: keys `x`, `y`, `radiusX`, `radiusY`, `force`, `id` | | `modifiers` | `integer` | no | ≥ 0; ≤ 15 | ## Replies | payload type | Fields | |---|---| | `hello` | `protocol`: `"browserhive.v1"`, `protocol_version`: `1`, `epoch`: `string`, `cursor`: `integer`, `server_version`: `string`, `now`: `integer` | | `subscribed` | `topic`: `string`, `from`: `integer`, `to`: `integer`, `complete`: `boolean` | | `unsubscribed` | `topic`: `string`, `ok`: `boolean` | | `pong` | `ts`: `integer` | | `screencast.started` | `topic`: `string`, `ordinal`: `integer` | | `ok` | `result?`: `any` | | `resync_required` | `topic?`: `string`, `reason`: one of `cursor_expired`, `epoch_changed`, `buffer_overflow` | ## Topics Subscribe with `{ "type": "subscribe", "topic": "", "cursor"?: }`. The reply `subscribed { from, to, complete }` says whether the replay was complete; `complete: false` (or `resync_required`) means reload from REST. | Topic | Scope | Events | |---|---|---| | `sessions` | `sessions:read` | [`session.opened`](#event-session-opened), [`session.updated`](#event-session-updated), [`session.closed`](#event-session-closed), [`session.removed`](#event-session-removed) | | `attention` | `attention:read` | [`attention.created`](#event-attention-created), [`attention.resolved`](#event-attention-resolved) | | `vault.confirm` | `vault:read` | [`vault.confirm.created`](#event-vault-confirm-created), [`vault.confirm.resolved`](#event-vault-confirm-resolved) | | `vault.config` | `vault:read` | [`vault.binding.changed`](#event-vault-binding-changed), [`vault.policy.changed`](#event-vault-policy-changed), [`vault.lock_state`](#event-vault-lock_state) | | `vault.access` | `vault:read` | [`vault.access`](#event-vault-access) | | `pages` | `sessions:read` | [`page.visited`](#event-page-visited) | | `blocklist` | `blocklist:read` | [`blocklist.hit`](#event-blocklist-hit), [`blocklist.reloaded`](#event-blocklist-reloaded) | | `system` | `system:read` | [`system.degraded`](#event-system-degraded), [`system.recovered`](#event-system-recovered), [`system.tick`](#event-system-tick), [`system.capacity`](#event-system-capacity), [`retention.completed`](#event-retention-completed) | | `logs` | `logs:read` | [`log.record`](#event-log-record) | | `notifications` | `notifications:read` | [`notification.created`](#event-notification-created), [`notification.updated`](#event-notification-updated) | | `session:` | `sessions:read` | [`session.opened`](#event-session-opened), [`session.updated`](#event-session-updated), [`session.closed`](#event-session-closed), [`session.removed`](#event-session-removed), [`session.warning`](#event-session-warning), [`tool.called`](#event-tool-called), [`page.visited`](#event-page-visited), [`screenshot.captured`](#event-screenshot-captured), [`vault.access`](#event-vault-access), [`blocklist.hit`](#event-blocklist-hit), [`attention.created`](#event-attention-created), [`attention.resolved`](#event-attention-resolved), [`vault.confirm.created`](#event-vault-confirm-created), [`vault.confirm.resolved`](#event-vault-confirm-resolved) | | `screencast:` | `sessions:read` | stream messages `meta`, `started`, `stopped`, `failed` and binary frames | ## Feed events Payloads of `kind: "event"` frames, discriminated on `type`. DTO fields (`session`, `row`, `request`, …) are the same shapes the REST API returns. ### `session.opened` | Field | Type | Required | Constraints | |---|---|---|---| | `session` | `object` | yes | keys `session_id`, `slug`, `owner`, `tenant_id`, `channel`, `engine`, `headless`, `incognito`, `persistence_mode`, `current_url`, `created_at`, `last_activity_at`, `closed_at`, `closed_reason`, `archived_at`, `lease_expires_at`, `lease_paused_at`, `lease_remaining_ms`, `state`, `live`, `disable_evaluate`, `vault_enabled`, `stealth`, `fingerprint`, `humanize`, `stealth_recorded`, `identity`, `proxy_label`, `counts`, `has_live_viewers`, `client` | ### `session.updated` | Field | Type | Required | Constraints | |---|---|---|---| | `session` | `object` | yes | keys `session_id`, `slug`, `owner`, `tenant_id`, `channel`, `engine`, `headless`, `incognito`, `persistence_mode`, `current_url`, `created_at`, `last_activity_at`, `closed_at`, `closed_reason`, `archived_at`, `lease_expires_at`, `lease_paused_at`, `lease_remaining_ms`, `state`, `live`, `disable_evaluate`, `vault_enabled`, `stealth`, `fingerprint`, `humanize`, `stealth_recorded`, `identity`, `proxy_label`, `counts`, `has_live_viewers`, `client` | ### `session.closed` | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | pattern `^([a-z][a-z0-9-]{1,31})-([0-9a-z]{8})$` | | `closed_at` | `integer` | yes | ≥ 0 | | `reason` | one of `user`, `operator`, `lease_expired`, `crash`, `shutdown`, `interrupted`, `launch_failed` | yes | — | ### `session.removed` | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | pattern `^([a-z][a-z0-9-]{1,31})-([0-9a-z]{8})$` | | `action` | one of `archived`, `unarchived`, `deleted` | yes | — | | `at` | `integer` | yes | ≥ 0 | ### `session.warning` | Field | Type | Required | Constraints | |---|---|---|---| | `session_id` | `string` | yes | pattern `^([a-z][a-z0-9-]{1,31})-([0-9a-z]{8})$` | | `code` | `string` | yes | — | | `message` | `string` | yes | — | | `details` | `any` | no | — | ### `tool.called` | Field | Type | Required | Constraints | |---|---|---|---| | `row` | `object` | yes | keys `event_id`, `session_id`, `tool`, `tab_id`, `ok`, `error_code`, `error_message`, `duration_ms`, `result_size_bytes`, `ts`, `trace_id`, `has_screenshot`, `args_json`, `result_text` | | `has_detail` | `boolean` | yes | — | ### `page.visited` | Field | Type | Required | Constraints | |---|---|---|---| | `row` | `object` | yes | keys `event_id`, `session_id`, `tab_id`, `url`, `title`, `domain`, `category`, `ts` | ### `screenshot.captured` | Field | Type | Required | Constraints | |---|---|---|---| | `row` | `object` | yes | keys `event_id`, `session_id`, `tool`, `kind`, `content_type`, `width`, `height`, `size_bytes`, `ts`, `url` | ### `vault.access` | Field | Type | Required | Constraints | |---|---|---|---| | `row` | `object` | yes | keys `event_id`, `session_id`, `session_slug`, `tool_event_id`, `entry_name`, `handle`, `result`, `reason`, `evaluate_enabled`, `page_url`, `origin_check`, `principal_id`, `details`, `ts` | ### `blocklist.hit` | Field | Type | Required | Constraints | |---|---|---|---| | `row` | `object` | yes | keys `event_id`, `session_id`, `session_slug`, `tool_event_id`, `url`, `domain`, `pattern`, `source`, `tool`, `ts` | ### `attention.created` | Field | Type | Required | Constraints | |---|---|---|---| | `request` | `object` | yes | keys `request_id`, `kind`, `session_id`, `session_slug`, `owner`, `reason`, `mode`, `options`, `status`, `message`, `resolved_by`, `resolution_reason`, `created_at`, `resolved_at`, `deadline_at`, `waited_ms`, `page_url`, `tool`, `event_id`, `entry_name` | ### `attention.resolved` | Field | Type | Required | Constraints | |---|---|---|---| | `request` | `object` | yes | keys `request_id`, `kind`, `session_id`, `session_slug`, `owner`, `reason`, `mode`, `options`, `status`, `message`, `resolved_by`, `resolution_reason`, `created_at`, `resolved_at`, `deadline_at`, `waited_ms`, `page_url`, `tool`, `event_id`, `entry_name` | ### `vault.confirm.created` | Field | Type | Required | Constraints | |---|---|---|---| | `request` | `object` | yes | keys `request_id`, `kind`, `session_id`, `session_slug`, `owner`, `reason`, `mode`, `options`, `status`, `message`, `resolved_by`, `resolution_reason`, `created_at`, `resolved_at`, `deadline_at`, `waited_ms`, `page_url`, `tool`, `event_id`, `entry_name` | ### `vault.confirm.resolved` | Field | Type | Required | Constraints | |---|---|---|---| | `request` | `object` | yes | keys `request_id`, `kind`, `session_id`, `session_slug`, `owner`, `reason`, `mode`, `options`, `status`, `message`, `resolved_by`, `resolution_reason`, `created_at`, `resolved_at`, `deadline_at`, `waited_ms`, `page_url`, `tool`, `event_id`, `entry_name` | ### `vault.binding.changed` | Field | Type | Required | Constraints | |---|---|---|---| | `handle` | `string` | yes | pattern `^[a-z0-9][a-z0-9._-]{0,127}$` | | `action` | one of `created`, `updated`, `removed` | yes | — | ### `vault.policy.changed` | Field | Type | Required | Constraints | |---|---|---|---| | `group_id` | `string` or `null` | yes | — | ### `vault.lock_state` | Field | Type | Required | Constraints | |---|---|---|---| | `unlocked` | `boolean` | yes | — | ### `blocklist.reloaded` | Field | Type | Required | Constraints | |---|---|---|---| | `patterns` | `integer` | yes | ≥ 0 | | `skipped` | `integer` | yes | ≥ 0 | | `loaded_at` | `integer` | yes | ≥ 0 | ### `system.degraded` | Field | Type | Required | Constraints | |---|---|---|---| | `event` | `object` | yes | keys `event_id`, `code`, `severity`, `message`, `details`, `first_seen_at`, `last_seen_at`, `count`, `resolved_at` | ### `system.recovered` | Field | Type | Required | Constraints | |---|---|---|---| | `event` | `object` | yes | keys `event_id`, `code`, `severity`, `message`, `details`, `first_seen_at`, `last_seen_at`, `count`, `resolved_at` | ### `system.tick` | Field | Type | Required | Constraints | |---|---|---|---| | `now` | `integer` | yes | ≥ 0 | ### `system.capacity` | Field | Type | Required | Constraints | |---|---|---|---| | `live` | `integer` | yes | ≥ 0 | | `max` | `integer` or `null` | yes | — | ### `retention.completed` | Field | Type | Required | Constraints | |---|---|---|---| | `at` | `integer` | yes | ≥ 0 | | `pruned_rows` | `integer` | yes | ≥ 0 | | `result` | one of `ok`, `partial`, `failed` | yes | — | | `severity` | one of `info`, `warn`, `error` | no | — | ### `notification.created` | Field | Type | Required | Constraints | |---|---|---|---| | `notification` | `object` | yes | keys `notification_id`, `principal_id`, `type`, `title`, `body`, `session_id`, `session_slug`, `target`, `source_event_id`, `created_at`, `updated_at`, `count`, `read_at`, `dismissed_at` | ### `notification.updated` | Field | Type | Required | Constraints | |---|---|---|---| | `notification` | `object` | yes | keys `notification_id`, `principal_id`, `type`, `title`, `body`, `session_id`, `session_slug`, `target`, `source_event_id`, `created_at`, `updated_at`, `count`, `read_at`, `dismissed_at` | ### `log.record` | Field | Type | Required | Constraints | |---|---|---|---| | `record` | `object` | yes | keys `seq`, `ts`, `level`, `msg`, `module`, `trace_id`, `span_id`, `request_id`, `session_id`, `principal`, `transport`, `err`; additional keys allowed | ## Screencast Start with `screencast.start { session_id, max_width?, max_height?, quality? }`; the reply `screencast.started` carries the `ordinal` that tags this screencast's binary frames. Frames are latest-wins and dropped under backpressure; each viewer may request its own size with `screencast.set_size`. ### Stream control messages | type | Fields | |---|---| | `meta` | `session_id`: `string`, `ordinal`: `integer`, `device_width`: `integer`, `device_height`: `integer`, `page_scale`: `number`, `offset_top`: `number` | | `started` | `session_id`: `string`, `ordinal`: `integer` | | `stopped` | `session_id`: `string`, `reason`: one of `stopped`, `session_closed`, `session_crashed`, `connection_closed` | | `failed` | `session_id`: `string`, `code`: `string`, `message?`: `string` | ### Binary frame header Each binary frame is a 16-byte big-endian header followed by the JPEG bytes. | Offset | Field | Encoding | |---|---|---| | 0 | `magic` | 4 ASCII bytes `BHSC` | | 4 | `ordinal` | u32 | | 8 | `seq` | u32 (monotonic per screencast; a lower seq after a higher one is dropped) | | 12 | `width` | u16 (JPEG width) | | 14 | `height` | u16 (JPEG height) |