OpenWAF Browsers has two APIs:
- The data plane at
https://browsers.openwaf.dev, which your agent calls with a browser key (bdk_…) to start, list and stop browsers and to manage profiles. - The management API under
https://dash.openwaf.dev/v1/orgs/{org}/browsers, which you call with an OpenWAF API token (owf_…) or the dashboard session to manage keys, settings, usage, sessions and the egress log. The CLI and the MCP tools use it.
Data plane
Base URL https://browsers.openwaf.dev. Every route needs a browser key, in either header:
X-API-Key: bdk_…
Authorization: Bearer bdk_…JSON in and out. A key sees only the browsers it started.
Start a browser
POST /v1/browsers. Every field is optional.
| Field | Type | Meaning |
|---|---|---|
profile | string | Id or name of a profile to load. One browser uses a profile at a time. |
metadata | object | Up to 10 key: value string pairs, to find the browser later (GET /v1/browsers?metadata=k=v). |
keepAlive | bool | Do not stop on the idle timeout: the browser runs until you stop it or it reaches the maximum session length. |
timeout | integer | Idle timeout in seconds: the browser stops after this long with no CDP or live view client. Default 120. Takes precedence over keepAlive. |
recording | bool | Record the session as MP4. The file appears in files after the browser stops. |
viewport | object | {"width": 1280, "height": 800} |
lang | string | Browser language, for example en-US or es-ES. |
webBotAuth | bool | Sign requests with Web Bot Auth. Default: the organization's setting, true unless an admin turned it off. |
The answer is the browser:
{"id": "bs_…", "status": "running",
"cdpUrl": "wss://browsers.openwaf.dev/cdp/…",
"cdpHttpUrl": "https://browsers.openwaf.dev/…",
"liveUrl": "https://browsers.openwaf.dev/…",
"urlsExpireAt": "2026-10-29T11:00:00Z", …}cdpUrlis the browser-level DevTools WebSocket:chromium.connectOverCDP(cdpUrl)in Playwright,puppeteer.connect({browserWSEndpoint: cdpUrl})in Puppeteer,npx @playwright/mcp@latest --cdp-endpoint <cdpUrl>for MCP.cdpHttpUrlserves the DevTools HTTP endpoints (/json/version,/json/list, …) with their WebSocket URLs pointing back throughbrowsers.openwaf.dev.liveUrlopens the screen in a web browser, to watch or take over.- The tokens in these URLs expire at
urlsExpireAt, 1 hour after they are issued.GET /v1/browsers/{id}returns fresh ones while the browser runs.
List and read browsers
GET /v1/browserslists the key's browsers. Filters:status=running,metadata=k=v(repeatable).GET /v1/browsers/{id}returns one browser, with fresh URLs while it runs.
Stop a browser
PATCH /v1/browsers/{id}
Content-Type: application/json
{"action": "stop"}Stopping finalizes the recording and saves and unlocks the profile. A browser also stops on its own:
- after its idle timeout (120 s by default) with no CDP or live view client;
- when it reaches the maximum session length (60 minutes in the beta);
- when Chrome exits;
- when its key is rotated or revoked, or the organization runs out of quota.
Files
GET /v1/browsers/{id}/files lists the downloads and the recording of a browser, each with a signed link that expires. Session files are kept for 7 days.
Egress log
GET /v1/browsers/{id}/egress returns {"items": […]} with the latest requests the browser made through the egress proxy: time, method, host, port, decision (allow, deny or error), the reason of a denial, status and bytes. The organization-wide log is in the management API.
Profiles
A profile keeps cookies, logins and local storage between browsers.
| Route | What it does |
|---|---|
POST /v1/profiles {"name": "…"} | Create a profile |
GET /v1/profiles | List profiles |
GET /v1/profiles/{id or name} | Read one profile |
PATCH /v1/profiles/{id or name} | Change it |
DELETE /v1/profiles/{id or name} | Delete it; 409 while a browser uses it |
Data plane errors
Errors are JSON: {"error": "…", "code": "…"}.
| Status | code | When |
|---|---|---|
| 400 | A field is invalid; error says which | |
| 401 | Missing, unknown, rotated or revoked key, or the key is disabled (organization over quota or not enabled) | |
| 404 | No such browser or profile for this key | |
| 409 | The profile is in use by another browser | |
| 429 | capacity_busy | Every browser slot of the beta is busy. Wait for Retry-After (30 seconds) and retry. |
| 429 | key_concurrency | This key already runs its maximum of browsers (2). Stop one first. |
| 503 | unavailable | The service is paused for maintenance. Retry later. |
A capacity_busy answer is shared capacity, not your quota: retrying after Retry-After is the right thing to do. A key_concurrency answer will not clear by waiting unless one of your browsers stops.
Management API
All routes are under https://dash.openwaf.dev/v1/orgs/{org}, with an API token (Authorization: Bearer owf_…) or the dashboard session. Reads need any role of the whole organization (readonly or higher). Keys and settings need admin. Stopping a session needs tech or higher. Writes take X-Change-Reason and are recorded in the audit log; they are not versioned configuration and do not take dry_run.
| Route | What it does |
|---|---|
GET /browsers | Overview: status, quota, settings, usage of the month, capacity and access requirements. Readable before access is approved. |
PATCH /browsers/settings | Change egress_mode, allow and web_bot_auth (any subset); answers the overview |
GET /browsers/keys | List keys |
POST /browsers/keys | Create a key: {"name": "ci-agent"}; answers 201 {"key", "secret"} |
POST /browsers/keys/{id}/rotate | New secret: {"key", "secret"}. The old secret stops at once and its live browsers end. |
DELETE /browsers/keys/{id} | Revoke a key (204); its live browsers end |
GET /browsers/usage | Usage series, per key and totals |
GET /browsers/sessions | Browsers of the organization; ?status=running |
POST /browsers/sessions/{id}/stop | Stop a browser; answers the session |
GET /browsers/egress | Egress log of the organization |
Overview
{"enabled": true, "status": "enabled", "available": true,
"api_url": "https://browsers.openwaf.dev", "plan": "beta",
"quota": {"browser_hours": 20, "egress_gb": 5, "concurrency": 2, "max_session_minutes": 60, "max_keys": 5},
"settings": {"egress_mode": "open", "allow": [], "web_bot_auth": true},
"period": {"start": "2026-10-01T00:00:00Z", "end": "2026-11-01T00:00:00Z"},
"usage": {"browser_seconds": 4310, "egress_bytes": 123456789, "sessions": 31, "denied_requests": 4},
"usage_updated_at": "2026-10-29T12:01:00Z",
"warning": false,
"capacity": {"max_sessions": 5, "running": 2, "busy": false},
"requirements": {"owners_email_verified": true, "owners_two_factor": true}}statusisenabled,not_enabledorquota_exceeded.warningistruewhen browser-hours or egress reach 80 % of the quota.capacityis the shared capacity of the service, ornullwhen it is not known.requirementssays whether every owner and admin has a verified email and two-factor authentication, which approval needs.
Settings
{"egress_mode": "allowlist", "allow": ["example.com", "*.example.com"], "web_bot_auth": true}| Field | Values |
|---|---|
egress_mode | open: any public host (the default). allowlist: only the hosts in allow. Private and special-purpose addresses are refused in both modes. |
allow | Up to 100 entries. example.com is that host only; *.example.com its subdomains. |
web_bot_auth | Default for new browsers. A browser can still set webBotAuth either way. |
Keys
{"id": "6f1c…", "name": "ci-agent", "prefix": "bdk_AbC1", "disabled": false,
"created_at": "2026-10-29T10:00:00Z", "created_by": "…", "rotated_at": null}The name matches ^[a-z0-9][a-z0-9-]{0,62}$ and is unique in the organization. disabled is true while the organization is over quota or not enabled. The secret of a create or rotate answer is shown once, with Cache-Control: no-store.
Usage
GET /browsers/usage?from=RFC3339&to=RFC3339&step=day|hour. Without parameters: the current month, by day.
{"from": "…", "to": "…", "step": "day",
"series": [{"t": "2026-10-29T00:00:00Z", "browser_seconds": 1800, "egress_bytes": 52428800, "sessions": 6, "denied_requests": 1}],
"by_key": [{"key_id": "6f1c…", "name": "ci-agent", "browser_seconds": 1800, "egress_bytes": 52428800, "sessions": 6, "denied_requests": 1}],
"totals": {"browser_seconds": 1800, "egress_bytes": 52428800, "sessions": 6, "denied_requests": 1},
"quota": {…}, "period": {"start": "…", "end": "…"}, "usage": {…}}usage is the month to date, the figure the quota is checked against. Usage is collected every minute, so the numbers lag by up to about two minutes.
Sessions
GET /browsers/sessions?status=running:
{"items": [{"id": "bs_…", "key_id": "6f1c…", "key_name": "ci-agent", "status": "running",
"created_at": "…", "started_at": "…", "stopped_at": null, "stop_reason": null,
"live_url": "https://browsers.openwaf.dev/…", "metadata": {"task": "quickstart"}}]}status is running, idle or stopped. live_url is a signed, view-only link, present only while the browser runs, valid for 1 hour.
Egress log
GET /browsers/egress?key=<id>&since=RFC3339&decision=deny&limit=200. Newest first; limit up to 1000, default 200.
{"items": [{"ts": "…", "key_id": "6f1c…", "key_name": "ci-agent", "session": "bs_…",
"method": "CONNECT", "host": "example.com", "port": 443, "decision": "allow", "reason": "",
"status": 200, "bytes_up": 1830, "bytes_down": 48211, "ms": 412}]}decision is allow, deny or error; reason says why a request was denied.
Management API errors
| Status | code | When |
|---|---|---|
| 403 | feature_disabled | OpenWAF Browsers is not enabled for the organization |
| 403 | forbidden | The role is too low, or the member or token is limited to a client or a zone |
| 404 | not_found | No such key or session in the organization |
| 409 | conflict | A key with that name already exists |
| 409 | key_limit | The organization already has its maximum of keys (5 by default); revoke one first |
| 422 | invalid | A field breaks the rules above; field names it |
| 503 | unavailable | The browser service cannot be reached right now |
Operator access
Approval is an operator action, not a customer route: an operator turns OpenWAF Browsers on for the organization with a reason and, if needed, a different quota. It fails while an owner or admin lacks a verified email or two-factor authentication. To ask for it, see get access.
CLI
openwaf-cloud ctl browsers … calls the management API. Every change takes --reason "…", shows what it will do and asks before applying it. --yes applies without asking (required without a terminal, as in CI); --dry-run only shows it. Reads take --json for the raw API answer. See get started with the CLI and changes.
| Command | What it does |
|---|---|
browsers status | The overview: approval, usage of the month against the quota, per-key limits, settings and shared capacity |
browsers settings [--egress-mode open|allowlist] [--allow a.example,*.example] [--web-bot-auth on|off] --reason "…" [--yes] | Change the settings (admin). --allow replaces the whole list; empty means none. Without options it shows the overview. |
browsers keys [list] | Keys, with prefix and state |
browsers keys create <name> --reason "…" [--yes] | Create a key (admin); the secret is shown once |
browsers keys rotate <key> --reason "…" [--yes] | New secret (admin); the current one stops at once. <key> is the name or the id. |
browsers keys revoke <key> --reason "…" [--yes] | Revoke a key (admin) |
browsers usage [--from RFC3339] [--to RFC3339] [--step day|hour] | Usage by day or hour and per key. --from defaults to the 1st of this month. |
browsers sessions [--status running|idle|stopped] | Browsers of the organization |
browsers sessions stop <id> --reason "…" [--yes] | Stop a browser (tech) |
browsers egress [--key <key>] [--since 1h|24h|7d|RFC3339] [--deny] [--limit N] | Requests of the browsers through the egress proxy. --deny keeps only denied ones; --limit defaults to 200, up to 1000. |
MCP tools
The OpenWAF MCP server has 10 tools for OpenWAF Browsers. Writes need a reason and follow the two steps of the other non-versioned tools: confirm=false only checks, confirm=true applies, without expected_version.
| Tool | Arguments | Role |
|---|---|---|
browser_status | readonly | |
browser_settings | none to read; egress_mode, allow, web_bot_auth, reason, confirm to change | admin to change |
browser_keys_list | readonly | |
browser_keys_create | name, reason, confirm; the secret is returned once | admin |
browser_keys_rotate | key, reason, confirm | admin |
browser_keys_revoke | key, reason, confirm | admin |
browser_sessions_list | status | readonly |
browser_sessions_stop | session, reason, confirm | tech |
browser_usage | from, to, step | readonly |
browser_egress_events | key, since, decision, limit | readonly |
The MCP tools manage the organization. To drive a browser from an assistant, use the Playwright MCP server with the browser's cdpUrl, as in the quickstart.