# Referencia de la API

> Rutas, campos y errores del plano de datos browsers.openwaf.dev, la API de gestión en dash.openwaf.dev, la CLI y las herramientas MCP.

OpenWAF Browsers tiene dos API:

- El **plano de datos** en `https://browsers.openwaf.dev`, al que tu agente llama con una clave de navegador (`bdk_…`) para arrancar, listar y parar navegadores y para gestionar perfiles.
- La **API de gestión** en `https://dash.openwaf.dev/v1/orgs/{org}/browsers`, a la que llamas con un token de la API de OpenWAF (`owf_…`) o la sesión del panel para gestionar claves, ajustes, uso, sesiones y el registro de salida. La usan la [CLI](#cli) y las [herramientas MCP](#mcp-tools).

## Plano de datos

URL base `https://browsers.openwaf.dev`. Cada ruta necesita una clave de navegador, en cualquiera de estas cabeceras:

```http
X-API-Key: bdk_…
Authorization: Bearer bdk_…
```

JSON de entrada y de salida. Una clave solo ve los navegadores que arrancó ella.

### Arrancar un navegador

`POST /v1/browsers`. Todos los campos son opcionales.

| Campo | Tipo | Significado |
|---|---|---|
| `profile` | string | Id o nombre del [perfil](#profiles) que se carga. Un perfil lo usa un navegador cada vez. |
| `metadata` | object | Hasta 10 pares `clave: valor` de texto, para encontrar el navegador después (`GET /v1/browsers?metadata=k=v`). |
| `keepAlive` | bool | No parar por inactividad: el navegador sigue hasta que lo paras o llega a la duración máxima de sesión. |
| `timeout` | integer | Tiempo de inactividad en segundos: el navegador se para tras ese tiempo sin ningún cliente CDP ni de vista en directo. Por defecto 120. Manda sobre `keepAlive`. |
| `recording` | bool | Graba la sesión en MP4. El archivo aparece en [archivos](#files) cuando el navegador se para. |
| `viewport` | object | `{"width": 1280, "height": 800}` |
| `lang` | string | Idioma del navegador, por ejemplo `en-US` o `es-ES`. |
| `webBotAuth` | bool | Firma las peticiones con [Web Bot Auth](https://developers.openwaf.dev/es/browsers/web-bot-auth/). Por defecto, el ajuste de la organización: `true` salvo que un administrador lo haya desactivado. |

La respuesta es el navegador:

```json
{"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", …}
```

- `cdpUrl` es el WebSocket de DevTools del navegador: `chromium.connectOverCDP(cdpUrl)` en Playwright, `puppeteer.connect({browserWSEndpoint: cdpUrl})` en Puppeteer, `npx @playwright/mcp@latest --cdp-endpoint <cdpUrl>` para MCP.
- `cdpHttpUrl` sirve los endpoints HTTP de DevTools (`/json/version`, `/json/list`, …) con sus URL de WebSocket apuntando de vuelta a `browsers.openwaf.dev`.
- `liveUrl` abre la pantalla en un navegador web, para mirar o tomar el control.
- Los tokens de estas URL caducan en `urlsExpireAt`, 1 hora después de emitirse. `GET /v1/browsers/{id}` devuelve otros nuevos mientras el navegador sigue en marcha.

### Listar y leer navegadores

- `GET /v1/browsers` lista los navegadores de la clave. Filtros: `status=running`, `metadata=k=v` (se puede repetir).
- `GET /v1/browsers/{id}` devuelve un navegador, con URL nuevas mientras sigue en marcha.

### Parar un navegador

```http
PATCH /v1/browsers/{id}
Content-Type: application/json

{"action": "stop"}
```

Al pararlo se cierra la grabación y se guarda y libera el perfil. Un navegador también se para solo:

- tras su tiempo de inactividad (120 s por defecto) sin ningún cliente CDP ni de vista en directo;
- al llegar a la duración máxima de sesión (60 minutos en la beta);
- cuando Chrome termina;
- cuando se rota o revoca su clave, o la organización agota la cuota.

### Archivos

`GET /v1/browsers/{id}/files` lista las descargas y la grabación de un navegador, cada una con un enlace firmado que caduca. Los archivos de sesión se guardan 7 días.

### Registro de salida

`GET /v1/browsers/{id}/egress` devuelve `{"items": […]}` con las últimas peticiones que hizo el navegador por el proxy de salida: hora, método, host, puerto, `decision` (`allow`, `deny` o `error`), el motivo de una denegación, estado y bytes. El registro de toda la organización está en la [API de gestión](#egress).

### Perfiles

Un perfil guarda cookies, sesiones iniciadas y almacenamiento local entre navegadores.

| Ruta | Qué hace |
|---|---|
| `POST /v1/profiles` `{"name": "…"}` | Crea un perfil |
| `GET /v1/profiles` | Lista los perfiles |
| `GET /v1/profiles/{id o nombre}` | Lee un perfil |
| `PATCH /v1/profiles/{id o nombre}` | Lo cambia |
| `DELETE /v1/profiles/{id o nombre}` | Lo borra; `409` mientras lo usa un navegador |

### Errores del plano de datos

Los errores son JSON: `{"error": "…", "code": "…"}`.

| Estado | `code` | Cuándo |
|---|---|---|
| 400 | | Un campo no es válido; `error` dice cuál |
| 401 | | Clave ausente, desconocida, rotada o revocada, o la clave está desactivada (organización por encima de la cuota o sin activar) |
| 404 | | No existe ese navegador o perfil para esta clave |
| 409 | | Otro navegador está usando el perfil |
| 429 | `capacity_busy` | Todos los huecos de navegador de la beta están ocupados. Espera lo que diga `Retry-After` (30 segundos) y reintenta. |
| 429 | `key_concurrency` | Esta clave ya tiene su máximo de navegadores en marcha (2). Para uno antes. |
| 503 | `unavailable` | El servicio está en pausa por mantenimiento. Reintenta más tarde. |

`capacity_busy` es capacidad compartida, no tu cuota: reintentar tras `Retry-After` es lo correcto. `key_concurrency` no se arregla esperando, salvo que se pare uno de tus navegadores.

## API de gestión

Todas las rutas cuelgan de `https://dash.openwaf.dev/v1/orgs/{org}`, con un token de la API (`Authorization: Bearer owf_…`) o la sesión del panel. Para leer basta cualquier rol de toda la organización (`readonly` o superior). Las claves y los ajustes necesitan `admin`. Parar una sesión necesita `tech` o superior. Las escrituras llevan `X-Change-Reason` y quedan en el [registro de auditoría](https://developers.openwaf.dev/es/change-control/audit-log/); no son configuración versionada y no aceptan `dry_run`.

| Ruta | Qué hace |
|---|---|
| `GET /browsers` | Resumen: estado, cuota, ajustes, uso del mes, capacidad y requisitos de acceso. Se puede leer antes de que se apruebe el acceso. |
| `PATCH /browsers/settings` | Cambia `egress_mode`, `allow` y `web_bot_auth` (cualquier subconjunto); responde con el resumen |
| `GET /browsers/keys` | Lista las claves |
| `POST /browsers/keys` | Crea una clave: `{"name": "ci-agent"}`; responde `201 {"key", "secret"}` |
| `POST /browsers/keys/{id}/rotate` | Secreto nuevo: `{"key", "secret"}`. El anterior deja de valer al momento y sus navegadores se paran. |
| `DELETE /browsers/keys/{id}` | Revoca una clave (`204`); sus navegadores se paran |
| `GET /browsers/usage` | Series de uso, por clave y totales |
| `GET /browsers/sessions` | Navegadores de la organización; `?status=running` |
| `POST /browsers/sessions/{id}/stop` | Para un navegador; responde con la sesión |
| `GET /browsers/egress` | Registro de salida de la organización |

### Resumen

```json
{"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}}
```

- `status` es `enabled`, `not_enabled` o `quota_exceeded`.
- `warning` es `true` cuando las horas de navegador o la salida llegan al 80 % de la cuota.
- `capacity` es la capacidad compartida del servicio, o `null` si no se conoce.
- `requirements` dice si cada propietario y cada administrador tiene el email verificado y la verificación en dos pasos, que la aprobación exige.

### Ajustes

```json
{"egress_mode": "allowlist", "allow": ["example.com", "*.example.com"], "web_bot_auth": true}
```

| Campo | Valores |
|---|---|
| `egress_mode` | `open`: cualquier host público (por defecto). `allowlist`: solo los hosts de `allow`. Las direcciones privadas y de uso especial se rechazan en los dos modos. |
| `allow` | Hasta 100 entradas. `example.com` es solo ese host; `*.example.com`, sus subdominios. |
| `web_bot_auth` | Valor por defecto para los navegadores nuevos. Un navegador puede fijar `webBotAuth` en cualquier sentido. |

### Claves

```json
{"id": "6f1c…", "name": "ci-agent", "prefix": "bdk_AbC1", "disabled": false,
 "created_at": "2026-10-29T10:00:00Z", "created_by": "…", "rotated_at": null}
```

El nombre cumple `^[a-z0-9][a-z0-9-]{0,62}$` y es único en la organización. `disabled` es `true` mientras la organización supera la cuota o no está activada. El `secret` de una respuesta de creación o rotación se enseña una sola vez, con `Cache-Control: no-store`.

### Uso

`GET /browsers/usage?from=RFC3339&to=RFC3339&step=day|hour`. Sin parámetros: el mes en curso, por día.

```json
{"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` es lo que va de mes, la cifra con la que se compara la cuota. El uso se recoge cada minuto, así que las cifras van con hasta unos dos minutos de retraso.

### Sesiones

`GET /browsers/sessions?status=running`:

```json
{"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` es `running`, `idle` o `stopped`. `live_url` es un enlace firmado de solo lectura, presente solo mientras el navegador está en marcha y válido 1 hora.

### Registro de salida

`GET /browsers/egress?key=<id>&since=RFC3339&decision=deny&limit=200`. Del más reciente al más antiguo; `limit` hasta 1000, por defecto 200.

```json
{"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` es `allow`, `deny` o `error`; `reason` dice por qué se denegó una petición.

### Errores de la API de gestión

| Estado | `code` | Cuándo |
|---|---|---|
| 403 | `feature_disabled` | OpenWAF Browsers no está activado para la organización |
| 403 | `forbidden` | El rol no basta, o el miembro o el token está limitado a un cliente o a una zona |
| 404 | `not_found` | No existe esa clave o sesión en la organización |
| 409 | `conflict` | Ya existe una clave con ese nombre |
| 409 | `key_limit` | La organización ya tiene su máximo de claves (5 por defecto); revoca una antes |
| 422 | `invalid` | Un campo incumple las reglas de arriba; `field` dice cuál |
| 503 | `unavailable` | Ahora mismo no se llega al servicio de navegadores |

### Acceso por operador

La aprobación la hace un operador, no es una ruta para clientes: un operador activa OpenWAF Browsers para la organización con un motivo y, si hace falta, otra cuota. Falla mientras algún propietario o administrador no tenga el email verificado o la verificación en dos pasos. Para pedirlo, mira [pedir acceso](https://developers.openwaf.dev/es/browsers/#get-access).

## CLI

`openwaf-cloud ctl browsers …` llama a la API de gestión. Cada cambio lleva `--reason "…"`, enseña lo que va a hacer y pregunta antes de aplicarlo. `--yes` aplica sin preguntar (obligatorio sin terminal, como en CI); `--dry-run` solo lo enseña. Las lecturas aceptan `--json` para la respuesta de la API tal cual. Mira [empezar con la CLI](https://developers.openwaf.dev/es/cli/get-started/) y [cambios](https://developers.openwaf.dev/es/cli/changes/#options).

| Comando | Qué hace |
|---|---|
| `browsers status` | El resumen: aprobación, uso del mes frente a la cuota, límites por clave, ajustes y capacidad compartida |
| `browsers settings [--egress-mode open\|allowlist] [--allow a.example,*.example] [--web-bot-auth on\|off] --reason "…" [--yes]` | Cambia los ajustes (`admin`). `--allow` sustituye la lista entera; vacío es ninguno. Sin opciones enseña el resumen. |
| `browsers keys [list]` | Claves, con prefijo y estado |
| `browsers keys create <nombre> --reason "…" [--yes]` | Crea una clave (`admin`); el secreto se enseña una sola vez |
| `browsers keys rotate <clave> --reason "…" [--yes]` | Secreto nuevo (`admin`); el actual deja de valer al momento. `<clave>` es el nombre o el id. |
| `browsers keys revoke <clave> --reason "…" [--yes]` | Revoca una clave (`admin`) |
| `browsers usage [--from RFC3339] [--to RFC3339] [--step day\|hour]` | Uso por día u hora y por clave. `--from` es por defecto el día 1 de este mes. |
| `browsers sessions [--status running\|idle\|stopped]` | Navegadores de la organización |
| `browsers sessions stop <id> --reason "…" [--yes]` | Para un navegador (`tech`) |
| `browsers egress [--key <clave>] [--since 1h\|24h\|7d\|RFC3339] [--deny] [--limit N]` | Peticiones de los navegadores por el proxy de salida. `--deny` deja solo las denegadas; `--limit` es 200 por defecto, hasta 1000. |

## Herramientas MCP

El [servidor MCP de OpenWAF](https://developers.openwaf.dev/es/mcp/) tiene 10 herramientas para OpenWAF Browsers. Las escrituras necesitan un `reason` y siguen los dos pasos de las demás herramientas no versionadas: `confirm=false` solo comprueba, `confirm=true` aplica, sin `expected_version`.

| Herramienta | Argumentos | Rol |
|---|---|---|
| `browser_status` | | `readonly` |
| `browser_settings` | ninguno para leer; `egress_mode`, `allow`, `web_bot_auth`, `reason`, `confirm` para cambiar | `admin` para cambiar |
| `browser_keys_list` | | `readonly` |
| `browser_keys_create` | `name`, `reason`, `confirm`; el secreto se devuelve una sola vez | `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` |

Las herramientas MCP gestionan la organización. Para manejar un navegador desde un asistente, usa el servidor MCP de Playwright con el `cdpUrl` del navegador, como en el [inicio rápido](https://developers.openwaf.dev/es/browsers/quickstart/#drive-it).
