|
| 1 | +# Browser Use Cloud — Programmatic Automation |
| 2 | + |
| 3 | +`https://api.browser-use.com/api/v3` (REST). All five endpoints below were |
| 4 | +exercised end-to-end on 2026-05-05 with a real `BROWSER_USE_API_KEY`; the |
| 5 | +companion script `cleanup-zombies.py` next to this file *is* the |
| 6 | +field-test — running it lists active browsers and stops zombies via the |
| 7 | +same wire calls the harness uses internally. |
| 8 | + |
| 9 | +This skill is for users who already start cloud browsers via |
| 10 | +`start_remote_daemon()` and want to manage the surrounding lifecycle — |
| 11 | +provisioning fleets, cleaning up zombies, listing what's running, sharing |
| 12 | +liveUrls — without clicking through `cloud.browser-use.com`. |
| 13 | + |
| 14 | +## Auth |
| 15 | + |
| 16 | +REST uses a custom header (not `Authorization: Bearer` — that path |
| 17 | +returns a generic 401 silently): |
| 18 | + |
| 19 | +```python |
| 20 | +import os |
| 21 | +HEADERS = { |
| 22 | + "X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"], |
| 23 | + "Content-Type": "application/json", |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +The key only authorises actions on browsers and profiles created under |
| 28 | +it — there are no organisation-level admin endpoints on the public API. |
| 29 | + |
| 30 | +## Endpoint reference |
| 31 | + |
| 32 | +All paths are under `https://api.browser-use.com/api/v3`. Verified status |
| 33 | +codes and shapes from 2026-05-05 below. |
| 34 | + |
| 35 | +### `POST /browsers` — provision a cloud browser |
| 36 | + |
| 37 | +Body (camelCase): |
| 38 | + |
| 39 | +| Key | Type | Notes | |
| 40 | +|---|---|---| |
| 41 | +| `profileId` | UUID | optional; logged-in cloud profile | |
| 42 | +| `profileName` | str | optional; resolved client-side | |
| 43 | +| `proxyCountryCode` | ISO2 | default `"us"`; pass `null` to disable BU proxy | |
| 44 | +| `timeout` | int | minutes, 1..240 | |
| 45 | +| `customProxy` | obj | `{host, port, username, password, ignoreCertErrors}` | |
| 46 | +| `browserScreenWidth` / `browserScreenHeight` | int | viewport | |
| 47 | +| `allowResizing` | bool | viewport user-resizable | |
| 48 | +| `enableRecording` | bool | session recording | |
| 49 | + |
| 50 | +Returns `201` with this shape (also returned by `GET /browsers/{id}`, |
| 51 | +`GET /browsers` items, and `PATCH /browsers/{id}`): |
| 52 | + |
| 53 | +```python |
| 54 | +{ |
| 55 | + "id": str, |
| 56 | + "status": str, # e.g. "active" |
| 57 | + "liveUrl": str, # host: live.browser-use.com (different from cloud.browser-use.com) |
| 58 | + "cdpUrl": str, # https:// — daemon converts to ws via /json/version |
| 59 | + "timeoutAt": str, # ISO 8601 UTC |
| 60 | + "startedAt": str, |
| 61 | + "finishedAt": None, # populated only after stop |
| 62 | + "proxyUsedMb": str, # STRING — cast to float before arithmetic |
| 63 | + "proxyCost": str, # STRING |
| 64 | + "browserCost": str, # STRING |
| 65 | + "agentSessionId": None, |
| 66 | + "recordingUrl": None, # str only when enableRecording=True at create |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +The `liveUrl` carries the cdp WebSocket as a `?wss=...` query param, so |
| 71 | +sharing the URL alone hands off a viewable session — no extra setup. |
| 72 | + |
| 73 | +### `PATCH /browsers/{id}` — stop (end billing) |
| 74 | + |
| 75 | +Body `{"action": "stop"}`. Returns `200` with the same browser object, |
| 76 | +but `liveUrl` and `cdpUrl` come back as `null` and `finishedAt` is |
| 77 | +populated. Use the returned `proxyCost` + `browserCost` for final cost. |
| 78 | +Always wrap caller code in `try/finally`; every billed minute counts. |
| 79 | + |
| 80 | +### `GET /browsers` — list active sessions |
| 81 | + |
| 82 | +Returns `200` and the standard envelope |
| 83 | +`{items: [...], totalItems, pageNumber, pageSize}`. `items[*]` matches |
| 84 | +the `POST /browsers` response shape. Already-finished browsers appear in |
| 85 | +the listing for a window with `finishedAt` populated — filter them out |
| 86 | +when computing age. |
| 87 | + |
| 88 | +### `GET /profiles?pageSize=N&pageNumber=N` — list cloud profiles |
| 89 | + |
| 90 | +`pageSize` caps at 100. Same envelope as `/browsers`. |
| 91 | + |
| 92 | +### `GET /profiles/{id}` — profile detail |
| 93 | + |
| 94 | +Returns the same shape as the listing items: |
| 95 | + |
| 96 | +```python |
| 97 | +{ |
| 98 | + "id": str, |
| 99 | + "userId": None, # null in observed responses |
| 100 | + "name": str, |
| 101 | + "lastUsedAt": str | None, # null until first use |
| 102 | + "createdAt": str, |
| 103 | + "updatedAt": str, |
| 104 | + "cookieDomains": list[str] | None, # null on freshly-created profiles |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +`browser_harness.admin.list_cloud_profiles()` already wraps the listing |
| 109 | ++ per-id GET; prefer it unless you need raw access. |
| 110 | + |
| 111 | +## Companion script: `cleanup-zombies.py` |
| 112 | + |
| 113 | +A self-contained operator script next to this file. Run it with: |
| 114 | + |
| 115 | +```bash |
| 116 | +BROWSER_USE_API_KEY=... python agent-workspace/domain-skills/browser-use-cloud/cleanup-zombies.py |
| 117 | +# stops every active browser older than 30 minutes (default) |
| 118 | + |
| 119 | +BROWSER_USE_API_KEY=... python .../cleanup-zombies.py --older-than 5 --dry-run |
| 120 | +# preview only; no PATCH /stop sent |
| 121 | +``` |
| 122 | + |
| 123 | +The script is the practical residue of the API verification — running it |
| 124 | +exercises four of the five endpoints (`GET /browsers`, plus |
| 125 | +`PATCH .../stop` per zombie). Use it as the live regression check |
| 126 | +whenever this skill is updated. |
| 127 | + |
| 128 | +## Dashboard navigation (when API isn't enough) |
| 129 | + |
| 130 | +The dashboard at `cloud.browser-use.com` requires a logged-in session; |
| 131 | +the unauthenticated root redirects to `/signup` (verified 2026-05-05). |
| 132 | +Beyond `/signup` the slugs below are *inferred from typical SaaS layout* |
| 133 | +— confirm in your own browser before relying on the literal paths: |
| 134 | + |
| 135 | +``` |
| 136 | +/signup (verified) |
| 137 | +/dashboard [verify] |
| 138 | +/browsers [verify] — likely the dashboard mirror of GET /browsers |
| 139 | +/browsers/<id> [verify] |
| 140 | +/profiles [verify] |
| 141 | +/api-keys [verify] |
| 142 | +``` |
| 143 | + |
| 144 | +There is no `/usage` page mirror — `GET /usage` on the API returns 404, |
| 145 | +so per-session cost has to come from each browser record (`proxyCost` + |
| 146 | +`browserCost`). The dashboard surfaces aggregate billing somewhere, but |
| 147 | +that's outside the API surface and not useful from inside `bh`. |
| 148 | + |
| 149 | +For dashboard scraping, attach to your real Chrome and read cookies: |
| 150 | + |
| 151 | +```python |
| 152 | +cookies = cdp("Network.getCookies", urls=["https://cloud.browser-use.com"]) |
| 153 | +parts = [c["name"] + "=" + c["value"] for c in cookies.get("cookies", [])] |
| 154 | +dash_headers = {"Cookie": "; ".join(parts), "Accept": "text/html,application/json"} |
| 155 | +``` |
| 156 | + |
| 157 | +Empty cookie jar = not logged in; open `cloud.browser-use.com` in your |
| 158 | +real Chrome once, then retry. |
| 159 | + |
| 160 | +## Traps to avoid |
| 161 | + |
| 162 | +- **Auth header name** is `X-Browser-Use-API-Key`. `Authorization: |
| 163 | + Bearer ...` silently fails with a generic 401. |
| 164 | +- **Cost fields are strings**, not numbers. `proxyCost`, `browserCost`, |
| 165 | + `proxyUsedMb` come back as quoted strings (`"0.0123"`); cast to |
| 166 | + `float` before arithmetic. |
| 167 | +- **`cookieDomains` can be `None`** on freshly-created profiles, despite |
| 168 | + what `admin.py:list_cloud_profiles`'s docstring says. Guard with |
| 169 | + `c or []`. |
| 170 | +- **`liveUrl` host is `live.browser-use.com`**, not |
| 171 | + `cloud.browser-use.com`. They're separate surfaces. |
| 172 | +- **`start_remote_daemon` overwrites `BU_CDP_WS`** in the daemon env; |
| 173 | + re-read from `browser["cdpUrl"]` if you need the value afterwards. |
| 174 | + (PR #300 stops `run.py` from clobbering an explicit `BU_CDP_URL`, but |
| 175 | + the daemon env still gets set.) |
| 176 | +- **`liveUrl` is single-session** — after stop, the URL no longer |
| 177 | + resolves; don't cache across calls. |
| 178 | +- **`_browser_use` has a 60s timeout** in `admin.py`; long-running ops |
| 179 | + (large profile sync) need their own polling. |
| 180 | +- **`profile-use` CLI is a separate install**: |
| 181 | + `curl -fsSL https://browser-use.com/profile.sh | sh`. |
| 182 | +- **`pageSize` caps at 100** silently — paginate via `pageNumber`. |
| 183 | + `totalItems` in the envelope lets you size loops up front. |
| 184 | +- **`proxyCountryCode` defaults to `"us"`** when omitted; pass `None` to |
| 185 | + disable BU proxy entirely. Wrong country = wrong egress IP = breaks |
| 186 | + geo-locked auth. |
| 187 | + |
| 188 | +## What this skill does NOT cover |
| 189 | + |
| 190 | +- **Billing / payment methods** — dashboard only, intentionally |
| 191 | + sensitive. |
| 192 | +- **Organisation / team admin** — outside the per-API-key surface. |
| 193 | +- **SDK features** — Browser Use ships official SDKs separately; this |
| 194 | + skill is the raw-HTTP path for power users inside `bh`. |
| 195 | +- **Cross-API-key reads** — every endpoint is scoped to the calling key. |
| 196 | + |
| 197 | +## Provenance |
| 198 | + |
| 199 | +Live-tested 2026-05-05 against `https://api.browser-use.com/api/v3`: |
| 200 | + |
| 201 | +| Endpoint | Method | Status | Notes | |
| 202 | +|---|---|---|---| |
| 203 | +| `/profiles?pageSize=100&pageNumber=1` | GET | 200 | shape verified | |
| 204 | +| `/profiles/{id}` | GET | 200 | `cookieDomains=None` observed on a fresh profile | |
| 205 | +| `/browsers` | POST | 201 | `liveUrl` host is `live.browser-use.com` | |
| 206 | +| `/browsers/{id}` (`{action:"stop"}`) | PATCH | 200 | returns final cost | |
| 207 | +| `/browsers` | GET | 200 | paginated `{items,totalItems,pageNumber,pageSize}` | |
| 208 | +| `/usage` | GET | 404 | **no public endpoint** | |
| 209 | +| `/` | GET | 404 | no root metadata | |
| 210 | + |
| 211 | +Companion script `cleanup-zombies.py` re-runs the listing + stop subset |
| 212 | +end-to-end and is the regression artefact for this skill. A full E2E |
| 213 | +loop (spawn → list → stop → re-list) was executed on 2026-05-05 against |
| 214 | +the production API and printed: |
| 215 | + |
| 216 | +``` |
| 217 | +[STOP] 3ac4c964-...-d3d3e1ad7508 age= 0.0min cost=$0.0020 |
| 218 | +summary: 1 active session(s), stopped 1 |
| 219 | +``` |
| 220 | + |
| 221 | +Re-running the script in `--dry-run` mode against an empty pool is the |
| 222 | +cheapest smoke test (no `PATCH /stop` calls, ~$0). |
0 commit comments