# REST and WebSocket API

Part of the Oya Browser docs. All of it: https://oyabrowser.com/docs.md

## REST API

All endpoints require `Authorization: Bearer YOUR_API_KEY` header (except health and register). Interactive API testing available at [/swagger](https://oyabrowser.com/swagger).

| Method | Endpoint | Description |
| --- | --- | --- |
| `GET` | `/health` | Server status + browser count |
| `POST` | `/register-key` | Register a new API key (`{ "key": "..." }`) |
| `GET` | `/browsers` | List your connected browsers |
| `POST` | `/browsers/start` | Start one (`{ "persona": "auto" }`), provider comes from your key; an endpoint another browser holds answers 409 `endpoint_in_use` |
| `GET` | `/browsers/:id` | One browser with counters, health and its recent activity |
| `POST` | `/browsers/:id/stop` | Stop it, destroys a cloud sandbox, releases a CDP session |
| `POST` | `/browsers/stop` | Bulk: `{ "ids": [...] }` or `{ "all": true }` |
| `GET` | `/fleet` | Totals by health, provider and persona; usage and limits |
| `POST` | `/browsers/:id/command` | Send command (`{ "action": "...", "params": {} }`) |
| `POST` | `/browsers/:id/chat` | Chat (`{ "messages": [...] }`) |
| `GET` | `/live/:id?ticket=...` | SSE live view frame stream (single-use ticket) |
| `GET/POST` | `/mcp/:id` | MCP Streamable HTTP endpoint |
| `GET/POST` | `/personas` | List or create personas |
| `DELETE` | `/personas/:id` | Delete a persona (409 while in use) |
| `PUT` | `/personas/:id` | Rename, set the cap or the proxy hint, never the device |
| `POST` | `/personas/:id/clone` | A new persona of the same kind of device |
| `POST` | `/personas/preview` | The fingerprint a set of choices would produce |
| `GET` | `/personas/options` | Platforms and their coherent timezones and locales |
| `PUT` | `/personas/:id/mfa` | Store a second factor |
| `POST` | `/browsers/:id/captcha` | Detect and clear a CAPTCHA |
| `POST` | `/browsers/:id/mfa` | Answer an MFA prompt |
| `GET` | `/usage` | This key's usage, bucketed by hour |
| `GET` | `/audit` | This key's audit history |
| `GET` | `/config` | This key's settings, credentials masked |
| `POST` | `/config` | Update this key's settings |

### Command API Reference

Send commands via `POST /browsers/:id/command`. Each action uses only specific params, the rest are ignored.

### Navigation Actions

| Action | Params | Description |
| --- | --- | --- |
| `navigate` | `url` (required) | Navigate to a URL |
| `open_tab` | `url` (optional) | Open a new tab |
| `switch_tab` | `tab_id` (required) | Activate a tab by ID |
| `close_tab` | `tab_id` (optional, defaults to active) | Close a tab |
| `list_tabs` | *none* | List all open tabs |

### Page Analysis Actions

| Action | Params | Description |
| --- | --- | --- |
| `analyze` | `format?` | Full page + numbered elements, as markdown (default), toon or jsonl |
| `read_page` | `selector` (optional), `limit` (default 50) | Lightweight element listing |
| `screenshot` | *none* | Capture page as PNG |

### Interaction Actions

| Action | Params | Description |
| --- | --- | --- |
| `click` | `selector` (e.g. `[data-ac-id="3"]`) | Click an element |
| `type` | `selector` + `text` | Type into an input |
| `press_key` | `key` (e.g. Enter, Tab, Escape) | Press a keyboard key |
| `scroll` | `direction` (up/down), `amount` (px, default 500) | Scroll the page |
| `wait` | `selector`, `timeout` (ms, default 10000) | Wait for element to appear |

### Examples

```
// Navigate to a page
{ "action": "navigate", "params": { "url": "https://google.com" } }

// Analyze current page (no params needed)
{ "action": "analyze" }

// Click element #3 from analyze results
{ "action": "click", "params": { "selector": "[data-ac-id=\"3\"]" } }

// Type into element #9
{ "action": "type", "params": { "selector": "[data-ac-id=\"9\"]", "text": "hello world" } }

// Press Enter
{ "action": "press_key", "params": { "key": "Enter" } }

// Scroll down
{ "action": "scroll", "params": { "direction": "down", "amount": 500 } }

// Screenshot (no params needed)
{ "action": "screenshot" }

// List all tabs
{ "action": "list_tabs" }

// Open new tab
{ "action": "open_tab", "params": { "url": "https://gmail.com" } }

// Switch to tab
{ "action": "switch_tab", "params": { "tab_id": 2 } }

// Close tab (omit tab_id to close active tab)
{ "action": "close_tab", "params": { "tab_id": 3 } }

// Wait for element
{ "action": "wait", "params": { "selector": ".results", "timeout": 10000 } }

// Read page elements (lightweight)
{ "action": "read_page", "params": { "limit": 20 } }
```

### Typical Workflow

```
1. navigate → go to the page
2. analyze  → understand the page, get element IDs
3. click / type / press_key / scroll → interact
4. analyze  → re-analyze after page changes (old IDs are invalid)
5. repeat until task is done
```

## WebSocket Protocol

Browsers connect via WebSocket at `wss://oyabrowser.com/ws`.

### Auth

First message from browser:

```
{ "type": "auth", "api_key": "...", "browser_id": "...", "browser_name": "..." }
```

Server responds:

```
{ "type": "auth_ok", "browser_id": "..." }
```

### Commands

Server → Browser:

```
{ "type": "cmd", "id": "uuid", "action": "analyze", "params": {} }
```

Browser → Server:

```
{ "type": "cmd_result", "id": "uuid", "ok": true, "data": { ... } }
```

### Ping/Pong

Both sides send `{ "type": "ping" }` and respond with `{ "type": "pong" }` every 15-20 seconds.

### Live Stream

Server → Browser: `{ "type": "stream_start", "fps": 2 }`

Browser → Server: `{ "type": "frame", "data": "data:image/jpeg;base64,..." }`

Server → Browser: `{ "type": "stream_stop" }`
