From 8e89e4a80886979159f28684c4da9d8fb3597440 Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 17:13:33 -0700 Subject: [PATCH 01/13] docs: fix x402 frontmatter parsing by using MDX comments HTML comments are not valid MDX, so Mintlify ignored the page's frontmatter and fell back to a filename-derived sidebar title (X402). Co-Authored-By: Claude Fable 5 --- docs/cloud/guides/x402.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cloud/guides/x402.mdx b/docs/cloud/guides/x402.mdx index 9a38defb..6a2beaf1 100644 --- a/docs/cloud/guides/x402.mdx +++ b/docs/cloud/guides/x402.mdx @@ -3,7 +3,7 @@ title: x402 (pay-per-request) description: "Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request." --- - +{/* prettier-ignore-start */} [x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. @@ -371,4 +371,4 @@ const client = new BrowserUse({ x402 }); - [Standard API key auth](/cloud/quickstart) — alternative if you don't want pay-per-use - [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) - +{/* prettier-ignore-end */} From ed46e63fbb97f51264b88b4567499d55b79c24bc Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 17:13:51 -0700 Subject: [PATCH 02/13] docs: add custom SVG icons to integration sidebar entries Brand icons for Anthropic (group), OpenClaw, Hermes, n8n; monochrome icons for MCP Server, Webhooks, and x402 (Phosphor coin). Mintlify renders custom icons as , so monochrome icons live under images/icons/mono/ and get theme-aware tinting via CSS filters in style.css. Also adds sidebarTitle to x402 to keep the label compact, and stages an OpenAI icon for future use. Co-Authored-By: Claude Fable 5 --- docs/cloud/guides/mcp-server.mdx | 1 + docs/cloud/guides/webhooks.mdx | 1 + docs/cloud/guides/x402.mdx | 2 + .../tutorials/integrations/hermes-agent.mdx | 1 + docs/cloud/tutorials/integrations/n8n.mdx | 1 + .../cloud/tutorials/integrations/openclaw.mdx | 1 + docs/docs.json | 1 + docs/images/icons/claude.svg | 1 + docs/images/icons/hermes-agent.svg | 9 + docs/images/icons/mono/coin.svg | 1 + docs/images/icons/mono/mcp.svg | 1 + docs/images/icons/mono/openai.svg | 1 + docs/images/icons/mono/webhooks.svg | 1 + docs/images/icons/n8n.svg | 1 + docs/images/icons/openclaw.svg | 242 ++++++++++++++++++ docs/style.css | 9 + 16 files changed, 274 insertions(+) create mode 100644 docs/images/icons/claude.svg create mode 100644 docs/images/icons/hermes-agent.svg create mode 100644 docs/images/icons/mono/coin.svg create mode 100644 docs/images/icons/mono/mcp.svg create mode 100644 docs/images/icons/mono/openai.svg create mode 100644 docs/images/icons/mono/webhooks.svg create mode 100644 docs/images/icons/n8n.svg create mode 100644 docs/images/icons/openclaw.svg diff --git a/docs/cloud/guides/mcp-server.mdx b/docs/cloud/guides/mcp-server.mdx index 230b9975..660b25d1 100644 --- a/docs/cloud/guides/mcp-server.mdx +++ b/docs/cloud/guides/mcp-server.mdx @@ -1,6 +1,7 @@ --- title: MCP Server description: "Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client." +icon: "/images/icons/mono/mcp.svg" --- ``` diff --git a/docs/cloud/guides/webhooks.mdx b/docs/cloud/guides/webhooks.mdx index 212a7cca..b24dd63a 100644 --- a/docs/cloud/guides/webhooks.mdx +++ b/docs/cloud/guides/webhooks.mdx @@ -1,6 +1,7 @@ --- title: Webhooks description: "Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring." +icon: "/images/icons/mono/webhooks.svg" --- Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). diff --git a/docs/cloud/guides/x402.mdx b/docs/cloud/guides/x402.mdx index 6a2beaf1..67a31d19 100644 --- a/docs/cloud/guides/x402.mdx +++ b/docs/cloud/guides/x402.mdx @@ -1,6 +1,8 @@ --- title: x402 (pay-per-request) +sidebarTitle: x402 description: "Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request." +icon: "/images/icons/mono/coin.svg" --- {/* prettier-ignore-start */} diff --git a/docs/cloud/tutorials/integrations/hermes-agent.mdx b/docs/cloud/tutorials/integrations/hermes-agent.mdx index 6a2b6c83..6100c7e0 100644 --- a/docs/cloud/tutorials/integrations/hermes-agent.mdx +++ b/docs/cloud/tutorials/integrations/hermes-agent.mdx @@ -1,6 +1,7 @@ --- title: Hermes Agent description: Give Hermes Agent cloud browser automation with Browser Use. +icon: "/images/icons/hermes-agent.svg" --- [Hermes Agent](https://github.com/nousresearch/hermes-agent) is an open-source, self-improving AI agent by Nous Research. It has built-in browser automation tools that work with local Chromium out of the box. Add Browser Use and those tools run on cloud browsers with anti-detect profiles, residential proxies in 195+ countries, and stealth browsing. diff --git a/docs/cloud/tutorials/integrations/n8n.mdx b/docs/cloud/tutorials/integrations/n8n.mdx index d9373c45..1fa4aac4 100644 --- a/docs/cloud/tutorials/integrations/n8n.mdx +++ b/docs/cloud/tutorials/integrations/n8n.mdx @@ -1,6 +1,7 @@ --- title: n8n description: Use Browser Use as an HTTP node in n8n workflows. +icon: "/images/icons/n8n.svg" --- Browser Use works with [n8n](https://n8n.io) as a standard HTTP integration — no custom nodes needed. diff --git a/docs/cloud/tutorials/integrations/openclaw.mdx b/docs/cloud/tutorials/integrations/openclaw.mdx index 4c195a11..e26b0473 100644 --- a/docs/cloud/tutorials/integrations/openclaw.mdx +++ b/docs/cloud/tutorials/integrations/openclaw.mdx @@ -1,6 +1,7 @@ --- title: OpenClaw description: Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill. +icon: "/images/icons/openclaw.svg" --- [OpenClaw](https://openclaw.ai) is a self-hosted gateway that connects chat apps like WhatsApp, Telegram, and Discord to AI coding agents. Add Browser Use and those agents get full browser automation — anti-detect profiles, CAPTCHA solving, residential proxies in 195+ countries, and stealth browsing out of the box. diff --git a/docs/docs.json b/docs/docs.json index 7b7fd70c..0fb2e9d8 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -116,6 +116,7 @@ "pages": [ { "group": "Anthropic", + "icon": "/images/icons/claude.svg", "pages": [ "cloud/tutorials/integrations/claude-code", "cloud/tutorials/integrations/claude-managed-agents" diff --git a/docs/images/icons/claude.svg b/docs/images/icons/claude.svg new file mode 100644 index 00000000..b80b4d5e --- /dev/null +++ b/docs/images/icons/claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/hermes-agent.svg b/docs/images/icons/hermes-agent.svg new file mode 100644 index 00000000..69c52572 --- /dev/null +++ b/docs/images/icons/hermes-agent.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/images/icons/mono/coin.svg b/docs/images/icons/mono/coin.svg new file mode 100644 index 00000000..67df5753 --- /dev/null +++ b/docs/images/icons/mono/coin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/mono/mcp.svg b/docs/images/icons/mono/mcp.svg new file mode 100644 index 00000000..13a25a90 --- /dev/null +++ b/docs/images/icons/mono/mcp.svg @@ -0,0 +1 @@ +Model Context Protocol \ No newline at end of file diff --git a/docs/images/icons/mono/openai.svg b/docs/images/icons/mono/openai.svg new file mode 100644 index 00000000..f2fb72ee --- /dev/null +++ b/docs/images/icons/mono/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/mono/webhooks.svg b/docs/images/icons/mono/webhooks.svg new file mode 100644 index 00000000..ef6ef92a --- /dev/null +++ b/docs/images/icons/mono/webhooks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/n8n.svg b/docs/images/icons/n8n.svg new file mode 100644 index 00000000..82f0a6da --- /dev/null +++ b/docs/images/icons/n8n.svg @@ -0,0 +1 @@ +n8n \ No newline at end of file diff --git a/docs/images/icons/openclaw.svg b/docs/images/icons/openclaw.svg new file mode 100644 index 00000000..00fa9b5e --- /dev/null +++ b/docs/images/icons/openclaw.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/style.css b/docs/style.css index a0b604db..b514ab15 100644 --- a/docs/style.css +++ b/docs/style.css @@ -90,6 +90,15 @@ th { +/* Monochrome sidebar icons (rendered as , so currentColor can't apply): + tint black source SVGs to match sidebar text in each theme */ +img[src^="/images/icons/mono/"] { + filter: invert(0.35); +} +.dark img[src^="/images/icons/mono/"] { + filter: invert(0.72); +} + /* Open Source pages: blue accent */ html[data-product="open-source"] { --primary: 37 99 235 !important; From c5839e0e12fecd07689cf0f8efd588e7b45cab5c Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 22:34:01 -0700 Subject: [PATCH 03/13] chore: gitignore harness state directories --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index af871fcc..4be8a07f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,12 @@ docs/.mintlify/ # Claude Code .claude/skills/ +# OMC / harness state (any directory level) +.omc/ +.harness/ +omc/ +harness/ + # Personal notes reagan_* .pnpm-store From b79ce0e9179ede85bdb56fe786e04c386b82370f Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 22:34:01 -0700 Subject: [PATCH 04/13] docs: publish OpenAPI spec at /openapi.json and reference it in llms indexes --- docs/cloud/api-reference.mdx | 10 + docs/cloud/api-v2-overview.mdx | 2 + docs/cloud/llms-full.txt | 14 + docs/cloud/llms.txt | 2 +- docs/cloud/openapi/v3.json | 457 +++ docs/generate-llms-txt.sh | 11 +- docs/llms-full.txt | 14 + docs/llms.txt | 2 +- docs/openapi.json | 5946 ++++++++++++++++++++++++++++++++ 9 files changed, 6455 insertions(+), 3 deletions(-) create mode 100644 docs/openapi.json diff --git a/docs/cloud/api-reference.mdx b/docs/cloud/api-reference.mdx index ec937066..d53395bb 100644 --- a/docs/cloud/api-reference.mdx +++ b/docs/cloud/api-reference.mdx @@ -21,6 +21,16 @@ Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/sett https://api.browser-use.com/api/v3 ``` +## OpenAPI spec + +The full API surface is published as a machine-readable OpenAPI 3.1 spec — use it to generate typed clients or validate payloads: + +``` +https://docs.browser-use.com/openapi.json +``` + +Also at [/cloud/openapi/v3.json](https://docs.browser-use.com/cloud/openapi/v3.json). Legacy v2 spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json). + ## Quick example ```bash Create a session diff --git a/docs/cloud/api-v2-overview.mdx b/docs/cloud/api-v2-overview.mdx index 0f0d9542..d3cf52dd 100644 --- a/docs/cloud/api-v2-overview.mdx +++ b/docs/cloud/api-v2-overview.mdx @@ -13,6 +13,8 @@ export BROWSER_USE_API_KEY=your_key Base URL: `https://api.browser-use.com/api/v2` +OpenAPI spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json) — legacy; new projects should use [v3](https://docs.browser-use.com/openapi.json). + --- Prefer the SDK? See the [Agent (v2) docs](/cloud/legacy/agent). diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 8da85e61..12704f34 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -1,5 +1,7 @@ # Browser Use Cloud — Full Documentation +> Machine-readable OpenAPI spec: https://docs.browser-use.com/openapi.json (v3, canonical — also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json). Dashboard: https://cloud.browser-use.com. Create an API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 + # Quick start Source: https://docs.browser-use.com/cloud/quickstart @@ -2990,6 +2992,16 @@ Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/sett https://api.browser-use.com/api/v3 ``` +## OpenAPI spec + +The full API surface is published as a machine-readable OpenAPI 3.1 spec — use it to generate typed clients or validate payloads: + +``` +https://docs.browser-use.com/openapi.json +``` + +Also at [/cloud/openapi/v3.json](https://docs.browser-use.com/cloud/openapi/v3.json). Legacy v2 spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json). + ## Quick example ```bash Create a session @@ -3036,6 +3048,8 @@ export BROWSER_USE_API_KEY=your_key Base URL: `https://api.browser-use.com/api/v2` +OpenAPI spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json) — legacy; new projects should use [v3](https://docs.browser-use.com/openapi.json). + --- Prefer the SDK? See the [Agent (v2) docs](https://docs.browser-use.com/cloud/legacy/agent). diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 8b1c30c1..67bb5b51 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -5,7 +5,7 @@ - Dashboard: https://cloud.browser-use.com - Create API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 - Docs: https://docs.browser-use.com -- OpenAPI spec (v3): https://docs.browser-use.com/cloud/openapi/v3.json +- OpenAPI spec (v3): https://docs.browser-use.com/openapi.json (also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json) - Chat UI example: https://docs.browser-use.com/cloud/tutorials/chat-ui — Full end-to-end example with live browser, streaming, auth. Best starting point to build a prototype. - Open-source repo: https://github.com/browser-use/browser-use — The open-source Python library. Note: the open-source API is different from the Cloud SDK. If you want the easiest path to production with managed infrastructure, use the Cloud SDK below. diff --git a/docs/cloud/openapi/v3.json b/docs/cloud/openapi/v3.json index a0b72175..6c405728 100644 --- a/docs/cloud/openapi/v3.json +++ b/docs/cloud/openapi/v3.json @@ -2667,6 +2667,200 @@ } } } + }, + "/search": { + "post": { + "tags": [ + "Search" + ], + "summary": "Search the web", + "description": "Run a web search and get back a ranked list of LLM-optimized results. Each successful request is billed to your project balance.", + "operationId": "search", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Standardized, ranked search results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponse" + } + } + } + }, + "400": { + "description": "Missing or invalid `query`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "402": { + "description": "Insufficient balance — add credits to continue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "429": { + "description": "Rate limit exceeded — retry later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "502": { + "description": "The search request failed — retry later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "503": { + "description": "Authentication or billing is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + } + } + } + }, + "/fetch": { + "post": { + "tags": [ + "Fetch" + ], + "summary": "Fetch a URL", + "description": "Execute an HTTP request through Browser Use's proxy infrastructure with Chrome TLS fingerprinting, so the request looks like genuine browser traffic.", + "operationId": "fetch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The fetched response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchResponse" + } + } + } + }, + "400": { + "description": "Missing or invalid request (e.g. no `url`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "402": { + "description": "Insufficient balance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "403": { + "description": "Request blocked (e.g. SSRF protection: private/internal address).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "502": { + "description": "The upstream request failed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "503": { + "description": "Authentication or balance service temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + } + } + } } }, "components": { @@ -5476,6 +5670,269 @@ "type": "object", "title": "RunTaskRequest", "description": "Create a new session, dispatch a task, or both.\n\n- **No `sessionId` + no `task`**: creates an idle session (useful for uploading files before running a task).\n- **No `sessionId` + `task`**: creates a new session and immediately runs the task.\n- **`sessionId` + `task`**: dispatches the task to an existing idle session.\n- **`sessionId` + no `task`**: returns 422 — a task is required when targeting an existing session." + }, + "SearchRequest": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "minLength": 1, + "description": "The search query in natural language. Returns a ranked set of relevant web results.", + "example": "latest research on protein folding" + } + } + }, + "SearchResult": { + "type": "object", + "required": [ + "url", + "content" + ], + "properties": { + "title": { + "type": "string", + "description": "Title of the source. Omitted when the source does not provide one.", + "example": "An Introduction to Protein Folding" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Canonical URL of the source.", + "example": "https://example.com/protein-folding" + }, + "published_date": { + "type": "string", + "description": "Publication date of the source (YYYY-MM-DD). Omitted when the source does not provide one.", + "example": "2026-04-08" + }, + "content": { + "type": "string", + "description": "Relevant excerpts from the source, joined into a single markdown string and optimized for LLM consumption.", + "example": "# An Introduction to Protein Folding\n\n..." + } + } + }, + "SearchResponse": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "description": "Ranked list of web results, most relevant first.", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + } + } + }, + "SearchError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "description": "A generic, provider-agnostic error message.", + "example": "insufficient balance" + } + } + }, + "FetchRetryConfig": { + "type": "object", + "description": "Retry behavior for failed requests.", + "properties": { + "count": { + "type": "integer", + "description": "Number of retry attempts.", + "default": 3 + }, + "on_status": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Status codes that trigger a retry.", + "default": [ + 500, + 502, + 503, + 504 + ] + }, + "backoff_ms": { + "type": "integer", + "description": "Initial backoff in milliseconds (exponential).", + "default": 100 + } + } + }, + "FetchRequest": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Target URL.", + "example": "https://example.com" + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS" + ], + "default": "GET", + "description": "HTTP method." + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Additional HTTP headers." + }, + "body": { + "type": "string", + "description": "Request body as text." + }, + "body_base64": { + "type": "string", + "description": "Request body as base64, for binary content." + }, + "content_type": { + "type": "string", + "description": "Override the Content-Type header." + }, + "output_format": { + "type": "string", + "enum": [ + "raw", + "markdown", + "structured", + "simplified" + ], + "default": "raw", + "description": "How to format the response body. `raw` returns the page unchanged; `markdown` returns clean readable text; `structured` returns a parsed object (title, links, headings, tables); `simplified` strips boilerplate." + }, + "follow_redirects": { + "type": "boolean", + "default": true, + "description": "Follow HTTP redirects." + }, + "max_redirects": { + "type": "integer", + "default": 10, + "description": "Maximum redirects to follow." + }, + "timeout_ms": { + "type": "integer", + "default": 30000, + "maximum": 120000, + "description": "Request timeout in milliseconds." + }, + "session_id": { + "type": "string", + "description": "Session ID. Requests sharing a session persist cookies and the proxy IP across calls." + }, + "proxy_country": { + "type": "string", + "default": "US", + "description": "ISO 3166-1 alpha-2 country code for proxy routing (e.g. `DE`)." + }, + "retry": { + "$ref": "#/components/schemas/FetchRetryConfig" + }, + "insecure_skip_verify": { + "type": "boolean", + "default": false, + "description": "Skip TLS certificate verification." + } + } + }, + "FetchResponse": { + "type": "object", + "properties": { + "status_code": { + "type": "integer", + "description": "HTTP status code." + }, + "status": { + "type": "string", + "description": "Full status string (e.g. \"200 OK\")." + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Response headers. Each value is a list of strings." + }, + "body": { + "type": "string", + "description": "Response body as text." + }, + "body_base64": { + "type": "string", + "description": "Response body as base64, when binary." + }, + "is_binary": { + "type": "boolean", + "description": "Whether the response is binary content." + }, + "final_url": { + "type": "string", + "description": "Final URL after redirects." + }, + "redirect_count": { + "type": "integer", + "description": "Number of redirects followed." + }, + "protocol": { + "type": "string", + "description": "HTTP protocol version (e.g. \"HTTP/2.0\")." + }, + "error": { + "type": "string", + "description": "Error message if the request failed." + } + } + }, + "FetchError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "description": "Error message." + }, + "code": { + "type": "integer", + "description": "HTTP status code." + }, + "details": { + "type": "string", + "description": "Additional error details." + } + } } }, "securitySchemes": { diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index 6d2b70a3..065da5be 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -143,6 +143,11 @@ generate_full() { echo "# Browser Use ${product} — Full Documentation" > "$out" echo "" >> "$out" + if [[ "$product" == "Cloud" ]]; then + echo "> Machine-readable OpenAPI spec: https://docs.browser-use.com/openapi.json (v3, canonical — also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json). Dashboard: https://cloud.browser-use.com. Create an API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1" >> "$out" + echo "" >> "$out" + fi + python3 -c " import json @@ -229,7 +234,7 @@ cat > "$CLOUD_INDEX" << 'HEADER' - Dashboard: https://cloud.browser-use.com - Create API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 - Docs: https://docs.browser-use.com -- OpenAPI spec (v3): https://docs.browser-use.com/cloud/openapi/v3.json +- OpenAPI spec (v3): https://docs.browser-use.com/openapi.json (also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json) - Chat UI example: https://docs.browser-use.com/cloud/tutorials/chat-ui — Full end-to-end example with live browser, streaming, auth. Best starting point to build a prototype. - Open-source repo: https://github.com/browser-use/browser-use — The open-source Python library. Note: the open-source API is different from the Cloud SDK. If you want the easiest path to production with managed infrastructure, use the Cloud SDK below. @@ -282,3 +287,7 @@ generate_full "open-source" "Open Source" "$OS_FULL" cp "$SCRIPT_DIR/llms.txt" "$SCRIPT_DIR/cloud/llms.txt" cp "$SCRIPT_DIR/llms-full.txt" "$SCRIPT_DIR/cloud/llms-full.txt" echo "Copied root llms files to cloud/" + +# Sync the canonical v3 spec to the docs root so it is served at /openapi.json +cp "$SCRIPT_DIR/cloud/openapi/v3.json" "$SCRIPT_DIR/openapi.json" +echo "Copied cloud/openapi/v3.json to openapi.json (root)" diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 8da85e61..12704f34 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -1,5 +1,7 @@ # Browser Use Cloud — Full Documentation +> Machine-readable OpenAPI spec: https://docs.browser-use.com/openapi.json (v3, canonical — also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json). Dashboard: https://cloud.browser-use.com. Create an API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 + # Quick start Source: https://docs.browser-use.com/cloud/quickstart @@ -2990,6 +2992,16 @@ Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/sett https://api.browser-use.com/api/v3 ``` +## OpenAPI spec + +The full API surface is published as a machine-readable OpenAPI 3.1 spec — use it to generate typed clients or validate payloads: + +``` +https://docs.browser-use.com/openapi.json +``` + +Also at [/cloud/openapi/v3.json](https://docs.browser-use.com/cloud/openapi/v3.json). Legacy v2 spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json). + ## Quick example ```bash Create a session @@ -3036,6 +3048,8 @@ export BROWSER_USE_API_KEY=your_key Base URL: `https://api.browser-use.com/api/v2` +OpenAPI spec: [/cloud/openapi/v2.json](https://docs.browser-use.com/cloud/openapi/v2.json) — legacy; new projects should use [v3](https://docs.browser-use.com/openapi.json). + --- Prefer the SDK? See the [Agent (v2) docs](https://docs.browser-use.com/cloud/legacy/agent). diff --git a/docs/llms.txt b/docs/llms.txt index 8b1c30c1..67bb5b51 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -5,7 +5,7 @@ - Dashboard: https://cloud.browser-use.com - Create API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 - Docs: https://docs.browser-use.com -- OpenAPI spec (v3): https://docs.browser-use.com/cloud/openapi/v3.json +- OpenAPI spec (v3): https://docs.browser-use.com/openapi.json (also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json) - Chat UI example: https://docs.browser-use.com/cloud/tutorials/chat-ui — Full end-to-end example with live browser, streaming, auth. Best starting point to build a prototype. - Open-source repo: https://github.com/browser-use/browser-use — The open-source Python library. Note: the open-source API is different from the Cloud SDK. If you want the easiest path to production with managed infrastructure, use the Cloud SDK below. diff --git a/docs/openapi.json b/docs/openapi.json new file mode 100644 index 00000000..6c405728 --- /dev/null +++ b/docs/openapi.json @@ -0,0 +1,5946 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Browser Use Public API v3", + "summary": "Browser Use session-based agent API (v3)", + "version": "3.0.0" + }, + "servers": [ + { + "url": "https://api.browser-use.com/api/v3", + "description": "Production server" + } + ], + "paths": { + "/sessions": { + "post": { + "tags": [ + "Sessions" + ], + "summary": "Create Session", + "description": "Create a session and/or dispatch a task.\n\n- Without session_id, without task: creates a new idle session (e.g. for file uploads).\n- Without session_id, with task: creates a new session and dispatches the task.\n- With session_id, with task: dispatches the task to an existing idle session.\n- With session_id, without task: 422 — task is required when targeting an existing session.\n\nIf keep_alive is false (default), the session auto-stops when the task finishes.\nIf keep_alive is true, the session stays idle after the task, ready for follow-ups.", + "operationId": "create_session_sessions_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__endpoints__api__v3__sessions__views__RunTaskRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "tags": [ + "Sessions" + ], + "summary": "List Sessions", + "description": "List sessions for the authenticated project.", + "operationId": "list_sessions_sessions_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "description": "Page number (1-indexed).", + "default": 1, + "title": "Page" + }, + "description": "Page number (1-indexed)." + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Number of sessions per page (max 100).", + "default": 20, + "title": "Page Size" + }, + "description": "Number of sessions per page (max 100)." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/sessions/{session_id}": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "Get Session", + "description": "Get session details. Use this to poll for task completion and output.", + "operationId": "get_session_sessions__session_id__get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Sessions" + ], + "summary": "Delete Session", + "description": "Soft-delete a session. Stops the sandbox first if still running.", + "operationId": "delete_session_sessions__session_id__delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/sessions/{session_id}/stop": { + "post": { + "tags": [ + "Sessions" + ], + "summary": "Stop Session", + "description": "Stop a session or the running task.\n\n- strategy=session (default): destroy sandbox entirely, session → stopped.\n- strategy=task: stop the running query, session stays alive (→ idle).", + "operationId": "stop_session_sessions__session_id__stop_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/StopSessionRequest" + }, + { + "type": "null" + } + ], + "title": "Body" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/sessions/{session_id}/messages": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "List Session Messages", + "description": "Return messages for a session with cursor-based pagination (chronological order).", + "operationId": "list_session_messages_sessions__session_id__messages_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "after", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "Cursor: return messages after this message ID", + "title": "After" + }, + "description": "Cursor: return messages after this message ID" + }, + { + "name": "before", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "Cursor: return messages before this message ID", + "title": "Before" + }, + "description": "Cursor: return messages before this message ID" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "description": "Maximum number of messages to return (max 100).", + "default": 10, + "title": "Limit" + }, + "description": "Maximum number of messages to return (max 100)." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/browsers": { + "get": { + "tags": [ + "Browsers" + ], + "summary": "List Browser Sessions", + "description": "Get paginated list of browser sessions with optional status filtering.", + "operationId": "list_browser_sessions_browsers_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Pagesize" + } + }, + { + "name": "pageNumber", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Pagenumber" + } + }, + { + "name": "filterBy", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/BrowserSessionStatus" + }, + { + "type": "null" + } + ], + "title": "Filterby" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserSessionListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Browsers" + ], + "summary": "Create Browser Session", + "description": "Create a new browser session.\n\n**Pricing:** Browser sessions are charged at $0.02/hour for all users.\n\nThe full rate is charged upfront when the session starts.\nWhen you stop the session, any unused time is automatically refunded proportionally.\n\nBilling is rounded up to the minute (minimum 1 minute).\nFor example, if you stop a session after 30 minutes, you'll be refunded half the charged amount.\n\n**Session Limits:**\n- All users: Up to 4 hours per session", + "operationId": "create_browser_session_browsers_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateBrowserSessionRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserSessionItemView" + } + } + } + }, + "403": { + "description": "Session timeout limit exceeded (maximum 4 hours)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionTimeoutLimitExceededError" + } + } + } + }, + "404": { + "description": "Profile not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileNotFoundError" + } + } + } + }, + "422": { + "description": "Request validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "429": { + "description": "Too many concurrent active sessions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TooManyConcurrentActiveSessionsError" + } + } + } + } + } + } + }, + "/browsers/{session_id}": { + "get": { + "tags": [ + "Browsers" + ], + "summary": "Get Browser Session", + "description": "Get detailed browser session information including status and URLs.", + "operationId": "get_browser_session_browsers__session_id__get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserSessionView" + } + } + } + }, + "404": { + "description": "Session not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionNotFoundError" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Browsers" + ], + "summary": "Update Browser Session", + "description": "Stop a browser session.\n\n**Refund:** When you stop a session, unused time is automatically refunded.\nIf the session ran for less than 1 hour, you'll receive a proportional refund.\nBilling is ceil to the nearest minute (minimum 1 minute).", + "operationId": "update_browser_session_browsers__session_id__patch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateBrowserSessionRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserSessionView" + } + } + } + }, + "404": { + "description": "Session not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionNotFoundError" + } + } + } + }, + "422": { + "description": "Request validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } + }, + "/browsers/{session_id}/downloads": { + "get": { + "tags": [ + "Browsers" + ], + "summary": "List Browser Session Downloads", + "description": "List files the browser downloaded to S3 during the session.\n\nPass ``includeUrls=true`` to receive presigned download URLs (15 min expiry) inline.\nFiles are stored at ``downloads/projects/{project_id}/sessions/{session_id}/`` in\nthe private bucket.", + "operationId": "list_browser_session_downloads_browsers__session_id__downloads_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Session Id" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 50, + "title": "Limit" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cursor" + } + }, + { + "name": "includeUrls", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Includeurls" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrowserDownloadListResponse" + } + } + } + }, + "404": { + "description": "Session not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionNotFoundError" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/profiles": { + "get": { + "tags": [ + "Profiles" + ], + "summary": "List Profiles", + "description": "Get paginated list of profiles.\n\nUse the `query` parameter to search profiles by name or user_id.\nThis is useful when you have many profiles and need to find a specific user.\n\nExample: If you set `user_id` to your internal user identifier when creating profiles,\nyou can later search for that user by passing their identifier as the `query` parameter.", + "operationId": "list_profiles_profiles_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Pagesize" + } + }, + { + "name": "pageNumber", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Pagenumber" + } + }, + { + "name": "query", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 200 + }, + { + "type": "null" + } + ], + "title": "Query" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Profiles" + ], + "summary": "Create Profile", + "description": "Profiles allow you to preserve the state of the browser between tasks.\n\nThey are most commonly used to allow users to preserve the log-in state in the agent between tasks.\nYou'd normally create one profile per user and then use it for all their tasks.\n\nYou can set a `user_id` when creating a profile to associate it with a user in your system.\nThis allows you to later search for the profile using the GET /profiles endpoint with a query parameter.", + "operationId": "create_profile_profiles_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileView" + } + } + } + }, + "402": { + "description": "Subscription required for additional profiles", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InsufficientCreditsError" + } + } + } + }, + "422": { + "description": "Request validation failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } + }, + "/profiles/{profile_id}": { + "get": { + "tags": [ + "Profiles" + ], + "summary": "Get Profile", + "description": "Get profile details.", + "operationId": "get_profile_profiles__profile_id__get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "profile_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Profile Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileView" + } + } + } + }, + "404": { + "description": "Profile not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileNotFoundError" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Profiles" + ], + "summary": "Update Profile", + "description": "Update a browser profile's information.", + "operationId": "update_profile_profiles__profile_id__patch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "profile_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Profile Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileView" + } + } + } + }, + "404": { + "description": "Profile not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProfileNotFoundError" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Profiles" + ], + "summary": "Delete Browser Profile", + "description": "Permanently delete a browser profile and its configuration.", + "operationId": "delete_browser_profile_profiles__profile_id__delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "profile_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Profile Id" + } + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/workspaces": { + "get": { + "tags": [ + "Workspaces" + ], + "summary": "List Workspaces", + "description": "Get paginated list of workspaces.", + "operationId": "list_workspaces_workspaces_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 10, + "title": "Pagesize" + } + }, + { + "name": "pageNumber", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "title": "Pagenumber" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Workspaces" + ], + "summary": "Create Workspace", + "description": "Create a new workspace for persistent shared file storage across sessions.", + "operationId": "create_workspace_workspaces_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceCreateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/workspaces/{workspace_id}": { + "get": { + "tags": [ + "Workspaces" + ], + "summary": "Get Workspace", + "description": "Get workspace details.", + "operationId": "get_workspace_workspaces__workspace_id__get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Workspaces" + ], + "summary": "Update Workspace", + "description": "Update a workspace's name.", + "operationId": "update_workspace_workspaces__workspace_id__patch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkspaceView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Workspaces" + ], + "summary": "Delete Workspace", + "description": "Delete a workspace and its S3 data.", + "operationId": "delete_workspace_workspaces__workspace_id__delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/workspaces/{workspace_id}/files": { + "get": { + "tags": [ + "Workspaces" + ], + "summary": "List Workspace Files", + "description": "List files in a workspace's S3 prefix.", + "operationId": "list_workspace_files_workspaces__workspace_id__files_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + }, + { + "name": "prefix", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "", + "title": "Prefix" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "minimum": 1, + "default": 50, + "title": "Limit" + } + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cursor" + } + }, + { + "name": "includeUrls", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Includeurls" + } + }, + { + "name": "shallow", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false, + "title": "Shallow" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Workspaces" + ], + "summary": "Delete Workspace File", + "description": "Delete a single file from a workspace.", + "operationId": "delete_workspace_file_workspaces__workspace_id__files_delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + }, + { + "name": "path", + "in": "query", + "required": true, + "schema": { + "type": "string", + "description": "Relative file path to delete", + "title": "Path" + }, + "description": "Relative file path to delete" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/workspaces/{workspace_id}/size": { + "get": { + "tags": [ + "Workspaces" + ], + "summary": "Get Workspace Size", + "description": "Get current storage usage for a workspace.", + "operationId": "get_workspace_size_workspaces__workspace_id__size_get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/workspaces/{workspace_id}/files/upload": { + "post": { + "tags": [ + "Workspaces" + ], + "summary": "Upload Workspace Files", + "description": "Get presigned PUT URLs for uploading files to a workspace.", + "operationId": "upload_workspace_files_workspaces__workspace_id__files_upload_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "workspace_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Workspace Id" + } + }, + { + "name": "prefix", + "in": "query", + "required": false, + "schema": { + "type": "string", + "description": "Directory prefix to upload into (e.g. \"uploads/\")", + "default": "", + "title": "Prefix" + }, + "description": "Directory prefix to upload into (e.g. \"uploads/\")" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileUploadRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileUploadResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/billing/account": { + "get": { + "tags": [ + "Billing" + ], + "summary": "Get Account Billing", + "description": "Get authenticated account information including credit balance and account details.", + "operationId": "get_account_billing_billing_account_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountView" + } + } + } + }, + "404": { + "description": "Project for a given API key not found!", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountNotFoundError" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/sizes": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "List Box Sizes", + "description": "Public catalog of available box sizes for the deploy picker.\n\nStatic data — no auth, no DB. Lives server-side (instead of being\nhardcoded in the frontend) so re-tiering or running a promo is a\none-file edit in `sizing.py` with no frontend redeploy needed.", + "operationId": "list_box_sizes_boxes_sizes_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxSizeListResponse" + } + } + } + } + } + } + }, + "/boxes/me/trial-eligibility": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Get Trial Eligibility", + "description": "Tell the FE whether to render a 'try free for 7 days' CTA.\n\nAccount-scoped — any user (free-tier or paid) without a prior\n`bux_trial_started_at` stamp is eligible for one free 7-day trial.\n\nReturns a tagged answer:\n - eligible=True → show the trial CTA on the empty-state.\n - eligible=False, reason='already_used' → show \"trial used,\n upgrade to deploy\".\n - eligible=False, reason='no_owner' → fallback for older projects\n we can't resolve an owner for; FE shows generic \"add credits\".\n\nCheap call: one profile lookup. The FE polls /me + /sizes already,\nthis is one more cheap GET on the deploy page.", + "operationId": "get_trial_eligibility_boxes_me_trial_eligibility_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TrialEligibilityView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Provision Box", + "description": "Provision a new box for the authenticated project.\n\nIdempotent-ish: if a live box already exists, returns 409. After `bux down`,\nthe row is reused (preserving profile_id + size — the user's previous\nchoice carries over unless they explicitly pass a new `size`).\n\nRequires a paying / comped project. Free-tier projects are rejected\nwith 402. Min-balance scales by size ($5 / $10 / $20 for small /\nmedium / large) so users with ~5 days of runway can deploy. See\n`common/models/bux/sizing.py`.", + "operationId": "provision_box_boxes_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/BoxCreateRequest" + }, + { + "type": "null" + } + ], + "title": "Body" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Get My Box", + "description": "Return the current box for the authenticated project. 404 if none.", + "operationId": "get_my_box_boxes_me_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + }, + "delete": { + "tags": [ + "Boxes" + ], + "summary": "Destroy My Box", + "description": "Terminate the EC2. Profile persists. Row is kept so `bux up` can reuse it.", + "operationId": "destroy_my_box_boxes_me_delete", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + }, + "patch": { + "tags": [ + "Boxes" + ], + "summary": "Patch My Box", + "description": "Update box flags. Currently only dsp_enabled (dangerously skip permissions).\n\nThe flag is persisted in DB and pushed to the box-agent over WS so the\n*next* shell launch picks it up. Existing terminal sessions keep their\noriginal setting until the user closes and reopens.", + "operationId": "patch_my_box_boxes_me_patch", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxPatchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/resize": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Resize My Box", + "description": "Resize the project's box to a larger tier (upgrade-only).\n\nThe actual stop → modify → start dance runs in a background task; this\nhandler returns immediately with status=RESIZING. The FE polls /me to\nwatch the box flip back to READY when the new instance is up. Takes\n~60-90s end-to-end (stop ~30s, start ~30s, growpart instant).\n\nConstraints enforced here:\n - new size must be a known tier\n - new size must be strictly larger than current (downsize → 400)\n - box must be in READY state (otherwise → 409)\n - project must clear the eligibility gate at the *new* size's\n min-balance (a small box upgrading to large is now playing by\n large-box rules)", + "operationId": "resize_my_box_boxes_me_resize_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxResizeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/restart": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Restart My Box", + "description": "Restart a box without destroying it.\n\nThe escape hatch from ERROR — before this endpoint, a box with a\nstale heartbeat (CPU spike / OOM / agent crash) could only be\ndestroyed by the user, which loses the EBS volume and forces a\nfresh setup. Now the user can:\n\n - mode=service (default): SSM `systemctl restart bux-tg`. ~5s.\n Fixes most cases (agent process crash, wedged long-poll).\n - mode=reboot: AWS rebootInstances. ~30-60s. Public IP and EBS\n preserved (only resize/upgrade changes the IP). Fallback for\n kernel-hung / SSM-unreachable boxes.\n\nAllowed when status is READY or ERROR. PROVISIONING / RESIZING /\nAWAITING_OAUTH / DESTROYED → 409 (no instance to restart, or a\nlong-running operation is already in flight).\n\nReturns the box view; status is unchanged here. The next bux_health\ntick + box-agent heartbeat is what authoritatively flips ERROR →\nREADY when the agent comes back up. The FE polls /me to watch.", + "operationId": "restart_my_box_boxes_me_restart_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxRestartRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BoxView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/shell": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Get Shell", + "description": "Mint a short-lived terminal URL. User opens it in a browser → live shell.\n\n`launch` controls what runs when the terminal connects:\n - \"bash\" (default) → plain login shell.\n - \"claude\" → auto-run the claude UI; Ctrl+C drops to bash.\n\n`w` (optional) selects which tmux window to attach to (e.g. `bux-w2`).\nDefaults to `bux-w1` — the box-agent auto-creates that window on first\nattach so single-window callers see no behavior change.", + "operationId": "get_shell_boxes_me_shell_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "launch", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "bash", + "title": "Launch" + } + }, + { + "name": "w", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "W" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShellResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/boxes/me/windows": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Windows List", + "operationId": "windows_list_boxes_me_windows_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindowListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + }, + "post": { + "tags": [ + "Boxes" + ], + "summary": "Windows Create", + "operationId": "windows_create_boxes_me_windows_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindowCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindowView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/windows/{window_id}": { + "delete": { + "tags": [ + "Boxes" + ], + "summary": "Windows Delete", + "operationId": "windows_delete_boxes_me_windows__window_id__delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "window_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Window Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Boxes" + ], + "summary": "Windows Rename", + "operationId": "windows_rename_boxes_me_windows__window_id__patch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "window_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Window Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindowRenameRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WindowView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/boxes/me/run": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Run Task", + "description": "Fire a one-shot `claude -p \"\"` on the box. Streams output as SSE.\n\nEvents:\n - data: {\"chunk\": \"...\"} — stdout chunks\n - event: done, data: {\"rc\": 0} — final exit code", + "operationId": "run_task_boxes_me_run_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RunTaskRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/update": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Update Box Agent", + "description": "Tell the box-agent to git-pull + restart from the OSS repo.\n\nDefault `branch` is whatever the box is currently tracking (the agent\nresolves it from `git rev-parse --abbrev-ref HEAD`); pass `branch=main`\nor `branch=stable` to switch tracks.\n\nReturns the old + new SHA so the FE can show \"updated to a3f9c1d\".\nThe agent restarts itself at the tail of the update — the next hello\nwill report the new version. We wait up to 90s for the update_result\nreply since git fetch + bootstrap.sh can stretch on slow networks.", + "operationId": "update_box_agent_boxes_me_update_post", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "branch", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Branch" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/boxes/me/tg": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Tg Install", + "description": "Install a Telegram bot on the box. Returns a deeplink the user taps to pair.", + "operationId": "tg_install_boxes_me_tg_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TelegramInstallRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TelegramInstallResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/tg/auto": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Tg Auto Start", + "description": "Kick off the QR-scan + scripted-CDP Telegram bot creation flow.\n\nIdempotent: if a non-terminal QR session already exists for this box,\nreturns it instead of spawning another browser. The FE keeps polling\n`GET /me/tg/auto/{id}` until the session reaches a terminal state.\n\nNo external BU Cloud API key is needed — the orchestrator provisions\nthe chromium VM via the cloud's internal browser service, which auths\nvia app_config.use_browser_api_key (already set in every env).", + "operationId": "tg_auto_start_boxes_me_tg_auto_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TgAutoStartResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/tg/auto/{qr_session_id}": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Tg Auto Status", + "description": "Poll a running QR session. Read-only — never advances the state\nmachine. Tenant-scoped: 404 if the session isn't owned by this project.", + "operationId": "tg_auto_status_boxes_me_tg_auto__qr_session_id__get", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "qr_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Qr Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TgAutoSessionView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Boxes" + ], + "summary": "Tg Auto Cancel", + "description": "User-initiated cancel. Marks the session failed/cancelled; the\ndriver task observes the terminal state on its next poll and exits its\nfinally-block (which stops the chromium VM).", + "operationId": "tg_auto_cancel_boxes_me_tg_auto__qr_session_id__delete", + "security": [ + { + "APIKeyHeader": [] + } + ], + "parameters": [ + { + "name": "qr_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Qr Session Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TgAutoSessionView" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/boxes/me/claude-login/start": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Claude Login Start", + "description": "Kick off a guided claude /login flow on the box.\n\nBox-agent forks `claude /login` as a pty subprocess, drives the\nOAuth picker, and ships the URL back over WS. The FE polls\nGET /me/claude-login/state and shows the URL as a tappable link\n+ a paste box for the OAuth callback code.\n\nCalling this again kills any in-flight attempt and starts fresh.\nThe user can also bail via POST /me/claude-login/cancel.", + "operationId": "claude_login_start_boxes_me_claude_login_start_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/claude-login/state": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Claude Login State Get", + "description": "Poll-based view of the active /login flow's state.", + "operationId": "claude_login_state_get_boxes_me_claude_login_state_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/claude-login/code": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Claude Login Code", + "description": "Pump the OAuth callback code into the box's pty stdin.\n\nNo real validation here — claude itself surfaces \"invalid code\"\non the next pty stdout line, which our claude_login_failed event\ncaptures.", + "operationId": "claude_login_code_boxes_me_claude_login_code_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClaudeLoginCodeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/me/claude-login/cancel": { + "post": { + "tags": [ + "Boxes" + ], + "summary": "Claude Login Cancel", + "operationId": "claude_login_cancel_boxes_me_claude_login_cancel_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "APIKeyHeader": [] + } + ] + } + }, + "/boxes/terminal/{token}": { + "get": { + "tags": [ + "Boxes" + ], + "summary": "Terminal Page", + "description": "HTML page the user opens in their browser → xterm.js terminal.\n\nRenders the current window_id into a script tag so the JS knows\nwhich tab is \"active\" without an extra round-trip. We don't render\nthe full window list here — the JS asks the parent for that on\nmount via the bux-react postMessage protocol, so the list always\nreflects the latest state (a tab created in another browser session\nsince the URL was minted shows up immediately).", + "operationId": "terminal_page_boxes_terminal__token__get", + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Token" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/search": { + "post": { + "tags": [ + "Search" + ], + "summary": "Search the web", + "description": "Run a web search and get back a ranked list of LLM-optimized results. Each successful request is billed to your project balance.", + "operationId": "search", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Standardized, ranked search results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponse" + } + } + } + }, + "400": { + "description": "Missing or invalid `query`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "402": { + "description": "Insufficient balance — add credits to continue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "429": { + "description": "Rate limit exceeded — retry later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "502": { + "description": "The search request failed — retry later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + }, + "503": { + "description": "Authentication or billing is temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchError" + } + } + } + } + } + } + }, + "/fetch": { + "post": { + "tags": [ + "Fetch" + ], + "summary": "Fetch a URL", + "description": "Execute an HTTP request through Browser Use's proxy infrastructure with Chrome TLS fingerprinting, so the request looks like genuine browser traffic.", + "operationId": "fetch", + "security": [ + { + "APIKeyHeader": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchRequest" + } + } + } + }, + "responses": { + "200": { + "description": "The fetched response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchResponse" + } + } + } + }, + "400": { + "description": "Missing or invalid request (e.g. no `url`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "402": { + "description": "Insufficient balance.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "403": { + "description": "Request blocked (e.g. SSRF protection: private/internal address).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "502": { + "description": "The upstream request failed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + }, + "503": { + "description": "Authentication or balance service temporarily unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FetchError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AccountNotFoundError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Account not found" + } + }, + "type": "object", + "title": "AccountNotFoundError", + "description": "Error response when an account is not found" + }, + "AccountView": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "The name of the user" + }, + "totalCreditsBalanceUsd": { + "type": "number", + "title": "Credits Balance USD", + "description": "The total credits balance in USD" + }, + "monthlyCreditsBalanceUsd": { + "type": "number", + "title": "Monthly Credits Balance USD", + "description": "Monthly subscription credits balance in USD" + }, + "additionalCreditsBalanceUsd": { + "type": "number", + "title": "Additional Credits Balance USD", + "description": "Additional top-up credits balance in USD" + }, + "rateLimit": { + "type": "integer", + "title": "Rate Limit", + "description": "The rate limit for the account" + }, + "planInfo": { + "$ref": "#/components/schemas/PlanInfo", + "title": "Plan Info", + "description": "The plan information" + }, + "isFreeTier": { + "type": "boolean", + "title": "Is Free Tier", + "description": "Whether the account is on the free tier", + "default": false + }, + "projectId": { + "type": "string", + "format": "uuid", + "title": "Project ID", + "description": "The ID of the project" + } + }, + "type": "object", + "required": [ + "totalCreditsBalanceUsd", + "monthlyCreditsBalanceUsd", + "additionalCreditsBalanceUsd", + "rateLimit", + "planInfo", + "projectId" + ], + "title": "AccountView", + "description": "View model for account information." + }, + "BoxCreateRequest": { + "properties": { + "profile_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Profile Id" + }, + "size": { + "anyOf": [ + { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ] + }, + { + "type": "null" + } + ], + "title": "Size" + } + }, + "type": "object", + "title": "BoxCreateRequest" + }, + "BoxCreateResponse": { + "properties": { + "box": { + "$ref": "#/components/schemas/BoxView" + } + }, + "type": "object", + "required": [ + "box" + ], + "title": "BoxCreateResponse" + }, + "BoxPatchRequest": { + "properties": { + "dsp_enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Dsp Enabled" + } + }, + "type": "object", + "title": "BoxPatchRequest" + }, + "BoxResizeRequest": { + "properties": { + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "title": "Size" + } + }, + "type": "object", + "required": [ + "size" + ], + "title": "BoxResizeRequest", + "description": "Resize an existing box to a larger tier.\n\nUpgrade-only: the backend rejects requests where `size` isn't strictly\nlarger than the box's current size. Downsize → 400 with a hint to\ndestroy + redeploy. Same size → 409 (idempotent failure — no work to do)." + }, + "BoxRestartMode": { + "type": "string", + "enum": [ + "service", + "reboot" + ], + "title": "BoxRestartMode", + "description": "How aggressive a /me/restart should be.\n\n`service` — fastest, fixes ~95% of cases (agent process crash, OOM,\n wedged TG long-poll). SSM `systemctl restart bux-tg`. ~5s.\n`reboot` — fallback for kernel-hung / SSM-unreachable boxes. AWS\n rebootInstances. ~30-60s. IP + EBS preserved." + }, + "BoxRestartRequest": { + "properties": { + "mode": { + "$ref": "#/components/schemas/BoxRestartMode", + "default": "service" + } + }, + "type": "object", + "title": "BoxRestartRequest" + }, + "BoxSizeListResponse": { + "properties": { + "sizes": { + "items": { + "$ref": "#/components/schemas/BoxSizeSpecView" + }, + "type": "array", + "title": "Sizes" + }, + "default": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "title": "Default" + } + }, + "type": "object", + "required": [ + "sizes", + "default" + ], + "title": "BoxSizeListResponse", + "description": "Catalog of available size tiers, returned to the deploy picker.\n\nKeeping the list server-side (instead of hardcoding prices in the\nfrontend) means the day we re-tier or run a promo, we don't have\nto ship a frontend deploy. Just edit `sizing.py`." + }, + "BoxSizeSpecView": { + "properties": { + "name": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "title": "Name" + }, + "vcpu": { + "type": "integer", + "title": "Vcpu" + }, + "ram_gb": { + "type": "integer", + "title": "Ram Gb" + }, + "disk_gb": { + "type": "integer", + "title": "Disk Gb" + }, + "daily_usd": { + "type": "number", + "title": "Daily Usd" + }, + "min_balance_usd": { + "type": "number", + "title": "Min Balance Usd" + } + }, + "type": "object", + "required": [ + "name", + "vcpu", + "ram_gb", + "disk_gb", + "daily_usd", + "min_balance_usd" + ], + "title": "BoxSizeSpecView", + "description": "User-visible spec sheet for one size tier. Returned alongside\nBoxView so the UI can render the tooltip (\"2 vCPU · 4 GB · 20 GB\")\nwithout a second round trip + so the picker on /bux can show all\nthree rows without us hardcoding prices in the frontend." + }, + "BoxStatus": { + "type": "string", + "enum": [ + "provisioning", + "awaiting_oauth", + "ready", + "resizing", + "error", + "halted", + "destroyed" + ], + "title": "BoxStatus" + }, + "BoxView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "project_id": { + "type": "string", + "format": "uuid", + "title": "Project Id" + }, + "profile_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Profile Id" + }, + "size": { + "type": "string", + "enum": [ + "small", + "medium", + "large" + ], + "title": "Size" + }, + "size_spec": { + "$ref": "#/components/schemas/BoxSizeSpecView" + }, + "ec2_instance_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ec2 Instance Id" + }, + "public_ip": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Public Ip" + }, + "status": { + "$ref": "#/components/schemas/BoxStatus" + }, + "status_detail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status Detail" + }, + "claude_authed": { + "type": "boolean", + "title": "Claude Authed" + }, + "tg_installed": { + "type": "boolean", + "title": "Tg Installed" + }, + "tg_bot_username": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tg Bot Username" + }, + "dsp_enabled": { + "type": "boolean", + "title": "Dsp Enabled" + }, + "live_browser_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Live Browser Url" + }, + "last_heartbeat_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Last Heartbeat At" + }, + "trial_ends_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Trial Ends At" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "project_id", + "profile_id", + "size", + "size_spec", + "ec2_instance_id", + "public_ip", + "status", + "status_detail", + "claude_authed", + "tg_installed", + "tg_bot_username", + "dsp_enabled", + "live_browser_url", + "last_heartbeat_at", + "created_at", + "updated_at" + ], + "title": "BoxView" + }, + "BrowserDownloadFile": { + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "File name (basename relative to the session downloads prefix)" + }, + "size": { + "type": "integer", + "title": "Size", + "description": "File size in bytes" + }, + "lastModified": { + "type": "string", + "format": "date-time", + "title": "Lastmodified", + "description": "When the file was last modified in S3" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url", + "description": "Presigned download URL (15 min expiry). Only included when `includeUrls=true`." + } + }, + "type": "object", + "required": [ + "path", + "size", + "lastModified" + ], + "title": "BrowserDownloadFile", + "description": "A single file the browser downloaded during the session." + }, + "BrowserDownloadListResponse": { + "properties": { + "files": { + "items": { + "$ref": "#/components/schemas/BrowserDownloadFile" + }, + "type": "array", + "title": "Files", + "description": "List of files downloaded by the browser" + }, + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Nextcursor", + "description": "Cursor for the next page. Pass as the `cursor` query parameter to fetch the next page." + }, + "hasMore": { + "type": "boolean", + "title": "Hasmore", + "description": "Whether there are more files beyond this page.", + "default": false + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "BrowserDownloadListResponse", + "description": "Paginated list of browser downloads with optional presigned URLs." + }, + "BrowserSessionItemView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "ID", + "description": "Unique identifier for the session" + }, + "status": { + "$ref": "#/components/schemas/BrowserSessionStatus", + "title": "Status", + "description": "Current status of the session (active/stopped)" + }, + "liveUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Live URL", + "description": "URL where the browser can be viewed live in real-time" + }, + "cdpUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "CDP URL", + "description": "Chrome DevTools Protocol URL for browser automation" + }, + "timeoutAt": { + "type": "string", + "format": "date-time", + "title": "Timeout At", + "description": "Timestamp when the session will timeout" + }, + "startedAt": { + "type": "string", + "format": "date-time", + "title": "Started At", + "description": "Timestamp when the session was created and started" + }, + "finishedAt": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Finished At", + "description": "Timestamp when the session was stopped (None if still active)" + }, + "proxyUsedMb": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxy Used MB", + "description": "Amount of proxy data used in MB", + "default": "0" + }, + "proxyCost": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxy Cost", + "description": "Cost of proxy usage in USD", + "default": "0" + }, + "browserCost": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Browser Cost", + "description": "Cost of browser session hosting in USD", + "default": "0" + }, + "agentSessionId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Agent Session ID", + "description": "ID of the agent session that created this browser (None for standalone BaaS sessions)" + }, + "recordingUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Recording URL", + "description": "Presigned URL to download the session recording (available after session ends, if recording was enabled)" + } + }, + "type": "object", + "required": [ + "id", + "status", + "timeoutAt", + "startedAt" + ], + "title": "BrowserSessionItemView", + "description": "View model for representing a browser session in list views." + }, + "BrowserSessionListResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/BrowserSessionItemView" + }, + "type": "array", + "title": "Items", + "description": "List of browser session views for the current page" + }, + "totalItems": { + "type": "integer", + "title": "Total Items", + "description": "Total number of items in the list" + }, + "pageNumber": { + "type": "integer", + "title": "Page Number", + "description": "Page number" + }, + "pageSize": { + "type": "integer", + "title": "Page Size", + "description": "Number of items per page" + } + }, + "type": "object", + "required": [ + "items", + "totalItems", + "pageNumber", + "pageSize" + ], + "title": "BrowserSessionListResponse", + "description": "Response model for paginated browser session list requests." + }, + "BrowserSessionStatus": { + "type": "string", + "enum": [ + "active", + "stopped" + ], + "title": "BrowserSessionStatus", + "description": "Enumeration of possible browser session states\n\nAttributes:\n ACTIVE: Session is currently active and running (browser is running)\n STOPPED: Session has been stopped and is no longer active (browser is stopped)" + }, + "BrowserSessionUpdateAction": { + "type": "string", + "enum": [ + "stop" + ], + "title": "BrowserSessionUpdateAction", + "description": "Available actions that can be performed on a browser session\n\nAttributes:\n STOP: Stop the browser session (cannot be undone)" + }, + "BrowserSessionView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "ID", + "description": "Unique identifier for the session" + }, + "status": { + "$ref": "#/components/schemas/BrowserSessionStatus", + "title": "Status", + "description": "Current status of the session (active/stopped)" + }, + "liveUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Live URL", + "description": "URL where the browser can be viewed live in real-time" + }, + "cdpUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "CDP URL", + "description": "Chrome DevTools Protocol URL for browser automation" + }, + "timeoutAt": { + "type": "string", + "format": "date-time", + "title": "Timeout At", + "description": "Timestamp when the session will timeout" + }, + "startedAt": { + "type": "string", + "format": "date-time", + "title": "Started At", + "description": "Timestamp when the session was created and started" + }, + "finishedAt": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Finished At", + "description": "Timestamp when the session was stopped (None if still active)" + }, + "proxyUsedMb": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxy Used MB", + "description": "Amount of proxy data used in MB", + "default": "0" + }, + "proxyCost": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxy Cost", + "description": "Cost of proxy usage in USD", + "default": "0" + }, + "browserCost": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Browser Cost", + "description": "Cost of browser session hosting in USD", + "default": "0" + }, + "agentSessionId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Agent Session ID", + "description": "ID of the agent session that created this browser (None for standalone BaaS sessions)" + }, + "recordingUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Recording URL", + "description": "Presigned URL to download the session recording (available after session ends, if recording was enabled)" + } + }, + "type": "object", + "required": [ + "id", + "status", + "timeoutAt", + "startedAt" + ], + "title": "BrowserSessionView", + "description": "View model for representing a browser session." + }, + "BuAgentSessionStatus": { + "type": "string", + "enum": [ + "created", + "idle", + "running", + "stopped", + "timed_out", + "error" + ], + "title": "BuAgentSessionStatus", + "description": "Session lifecycle status. Progresses through: created → idle → running → idle → ... → stopped / timed_out / error.\n\n- `created`: Sandbox is starting up. The session is not yet ready to accept tasks.\n- `idle`: Sandbox is healthy and waiting for a task. You can dispatch a task or upload files.\n- `running`: A task is currently being executed by the agent.\n- `stopped`: Session was stopped — either explicitly via the stop endpoint, or automatically after task completion (when `keepAlive` is false).\n- `timed_out`: Session was cleaned up due to inactivity timeout.\n- `error`: Sandbox failed to start or encountered an unrecoverable error." + }, + "BuModel": { + "type": "string", + "enum": [ + "bu-mini", + "bu-max", + "bu-ultra", + "gemini-3-flash", + "claude-sonnet-4.6", + "claude-opus-4.6", + "claude-opus-4.7", + "gpt-5.4-mini" + ], + "title": "BuModel", + "description": "The model to use for the agent. Each model has different capabilities and pricing.\n\n- `bu-mini` / `gemini-3-flash`: Gemini 3 Flash — fast and cost-effective. Best for simple, well-defined tasks like form filling or data extraction.\n- `bu-max` / `claude-sonnet-4.6`: Claude Sonnet 4.6 — balanced performance. Best for multi-step workflows that require reasoning and decision-making.\n- `bu-ultra` / `claude-opus-4.6`: Claude Opus 4.6 — capable general-purpose Opus tier.\n- `claude-opus-4.7`: Claude Opus 4.7 — most capable. Best for complex tasks that require advanced reasoning, long-horizon planning, or handling ambiguous instructions.\n- `gpt-5.4-mini`: GPT-5.4 mini — OpenAI's fast and efficient model. Best for tasks that benefit from OpenAI's capabilities." + }, + "ClaudeLoginCodeRequest": { + "properties": { + "code": { + "type": "string", + "title": "Code" + } + }, + "type": "object", + "required": [ + "code" + ], + "title": "ClaudeLoginCodeRequest" + }, + "CreateBrowserSessionRequest": { + "properties": { + "profileId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Profile ID", + "description": "The ID of the profile to use for the session" + }, + "proxyCountryCode": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProxyCountryCode" + }, + { + "type": "null" + } + ], + "title": "Proxy Country Code", + "description": "Country code for proxy location. Defaults to US. Set to null to disable proxy.", + "default": "us" + }, + "timeout": { + "type": "integer", + "title": "Timeout", + "description": "The timeout for the session in minutes. All users can use up to 240 minutes (4 hours). Browser sessions are charged $0.02/hour.", + "default": 60, + "ge": 1, + "le": 240 + }, + "browserScreenWidth": { + "anyOf": [ + { + "type": "integer", + "maximum": 6144.0, + "minimum": 320.0 + }, + { + "type": "null" + } + ], + "title": "Browser Screen Width", + "description": "Custom screen width in pixels for the browser." + }, + "browserScreenHeight": { + "anyOf": [ + { + "type": "integer", + "maximum": 3456.0, + "minimum": 320.0 + }, + { + "type": "null" + } + ], + "title": "Browser Screen Height", + "description": "Custom screen height in pixels for the browser." + }, + "allowResizing": { + "type": "boolean", + "title": "Allow Resizing", + "description": "Whether to allow the browser to be resized during the session (not recommended since it reduces stealthiness).", + "default": false + }, + "customProxy": { + "anyOf": [ + { + "$ref": "#/components/schemas/CustomProxy" + }, + { + "type": "null" + } + ], + "title": "Custom Proxy", + "description": "Custom proxy settings to use for the session. If not provided, our proxies will be used. Custom proxies are available on any active subscription." + }, + "enableRecording": { + "type": "boolean", + "title": "Enable Recording", + "description": "If True, enables session recording. Defaults to False.", + "default": false + } + }, + "type": "object", + "title": "CreateBrowserSessionRequest", + "description": "Request model for creating a browser session." + }, + "CustomProxy": { + "properties": { + "host": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Host", + "description": "Host of the proxy." + }, + "port": { + "type": "integer", + "maximum": 65535.0, + "minimum": 1.0, + "title": "Port", + "description": "Port of the proxy." + }, + "username": { + "anyOf": [ + { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Username", + "description": "Username for proxy authentication." + }, + "password": { + "anyOf": [ + { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + { + "type": "null" + } + ], + "title": "Password", + "description": "Password for proxy authentication." + }, + "ignoreCertErrors": { + "type": "boolean", + "title": "Ignore Certificate Errors", + "description": "Ignore TLS certificate errors. Enable this if your proxy uses a self-signed or untrusted certificate (e.g. Burp Suite, corporate proxies).", + "default": false + } + }, + "type": "object", + "required": [ + "host", + "port" + ], + "title": "CustomProxy", + "description": "Request model for creating a custom proxy." + }, + "FileInfo": { + "properties": { + "path": { + "type": "string", + "title": "Path", + "description": "File path relative to the session workspace root." + }, + "size": { + "type": "integer", + "title": "Size", + "description": "File size in bytes." + }, + "lastModified": { + "type": "string", + "format": "date-time", + "title": "Lastmodified", + "description": "When the file was last modified." + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url", + "description": "Presigned download URL (60s expiry). Only included when `includeUrls=true`." + } + }, + "type": "object", + "required": [ + "path", + "size", + "lastModified" + ], + "title": "FileInfo", + "description": "A file in a session's workspace." + }, + "FileListResponse": { + "properties": { + "files": { + "items": { + "$ref": "#/components/schemas/FileInfo" + }, + "type": "array", + "title": "Files" + }, + "folders": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Folders", + "description": "Immediate sub-folder names at this prefix level" + }, + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Nextcursor", + "description": "Cursor for the next page. Pass as the `cursor` query parameter to fetch the next page." + }, + "hasMore": { + "type": "boolean", + "title": "Hasmore", + "description": "Whether there are more files beyond this page.", + "default": false + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "FileListResponse", + "description": "Paginated file listing with optional presigned download URLs." + }, + "FileUploadItem": { + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1, + "title": "Name", + "description": "Filename, e.g. \"data.csv\"" + }, + "contentType": { + "type": "string", + "maxLength": 255, + "title": "Contenttype", + "description": "MIME type, e.g. \"text/csv\"", + "default": "application/octet-stream" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Size", + "description": "File size in bytes (required for workspace uploads)" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "FileUploadItem", + "description": "A single file to upload." + }, + "FileUploadRequest": { + "properties": { + "files": { + "items": { + "$ref": "#/components/schemas/FileUploadItem" + }, + "type": "array", + "maxItems": 10, + "minItems": 1, + "title": "Files" + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "FileUploadRequest", + "description": "Request body for generating presigned upload URLs." + }, + "FileUploadResponse": { + "properties": { + "files": { + "items": { + "$ref": "#/components/schemas/FileUploadResponseItem" + }, + "type": "array", + "title": "Files" + } + }, + "type": "object", + "required": [ + "files" + ], + "title": "FileUploadResponse", + "description": "Presigned upload URLs for the requested files." + }, + "FileUploadResponseItem": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Original filename as requested." + }, + "uploadUrl": { + "type": "string", + "title": "Uploadurl", + "description": "Presigned PUT URL. Upload the file by sending a PUT request to this URL with the file content and matching Content-Type header. Expires after 5 minutes." + }, + "path": { + "type": "string", + "title": "Path", + "description": "Path where the file will be stored in the workspace, e.g. \"uploads/data.csv\"." + } + }, + "type": "object", + "required": [ + "name", + "uploadUrl", + "path" + ], + "title": "FileUploadResponseItem", + "description": "Presigned upload URL for a single file." + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "InsufficientCreditsError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Insufficient credits" + } + }, + "type": "object", + "title": "InsufficientCreditsError", + "description": "Error response when there are insufficient credits" + }, + "MessageListResponse": { + "properties": { + "messages": { + "items": { + "$ref": "#/components/schemas/MessageResponse" + }, + "type": "array", + "title": "Messages", + "description": "List of messages in chronological order." + }, + "hasMore": { + "type": "boolean", + "title": "Hasmore", + "description": "Whether there are more messages available beyond this page. Use cursor-based pagination with the `after` or `before` query parameters to fetch more." + } + }, + "type": "object", + "required": [ + "messages", + "hasMore" + ], + "title": "MessageListResponse" + }, + "MessageResponse": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id", + "description": "Unique message identifier." + }, + "sessionId": { + "type": "string", + "format": "uuid", + "title": "Sessionid", + "description": "ID of the session this message belongs to." + }, + "role": { + "type": "string", + "title": "Role", + "description": "Message role: \"human\" for user-submitted tasks, \"ai\" for agent actions and responses." + }, + "data": { + "type": "string", + "title": "Data", + "description": "Raw message content. Format depends on the message type — may be plain text, JSON, or structured action data." + }, + "type": { + "type": "string", + "title": "Type", + "description": "Message category. Common values: `user_message`, `assistant_message`, `browser_action`, `file_operation`, `code_execution`, `integration`, `planning`, `completion`, `browser_action_result`, `browser_action_error`.", + "default": "" + }, + "summary": { + "type": "string", + "title": "Summary", + "description": "One-liner human-readable description of the message (e.g. \"Navigating to google.com\", \"Clicking element #5\"). Useful for building activity feeds.", + "default": "" + }, + "screenshotUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Screenshoturl", + "description": "Browser screenshot captured at the time of this message. Presigned URL, expires after 5 minutes." + }, + "hidden": { + "type": "boolean", + "title": "Hidden", + "description": "Whether this message should be hidden from the user in a chat UI.", + "default": false + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat", + "description": "When this message was created." + } + }, + "type": "object", + "required": [ + "id", + "sessionId", + "role", + "data", + "createdAt" + ], + "title": "MessageResponse", + "description": "A single message from the session's message stream.\n\nMessages represent the agent's actions, observations, and decisions as it executes a task." + }, + "PlanInfo": { + "properties": { + "planName": { + "type": "string", + "title": "Plan Name", + "description": "The name of the plan" + }, + "subscriptionStatus": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subscription Status", + "description": "The status of the subscription" + }, + "subscriptionId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subscription ID", + "description": "The ID of the subscription" + }, + "subscriptionCurrentPeriodEnd": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subscription Current Period End", + "description": "The end of the current period" + }, + "subscriptionCanceledAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Subscription Canceled At", + "description": "The date the subscription was canceled" + } + }, + "type": "object", + "required": [ + "planName", + "subscriptionStatus", + "subscriptionId", + "subscriptionCurrentPeriodEnd", + "subscriptionCanceledAt" + ], + "title": "PlanInfo", + "description": "View model for plan information" + }, + "ProfileCreateRequest": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the profile" + }, + "userId": { + "anyOf": [ + { + "type": "string", + "maxLength": 255 + }, + { + "type": "null" + } + ], + "title": "User ID", + "description": "Your internal user identifier for this profile. Use this to associate a profile with a user in your system." + } + }, + "type": "object", + "title": "ProfileCreateRequest", + "description": "Request model for creating a new profile." + }, + "ProfileListResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ProfileView" + }, + "type": "array", + "title": "Items", + "description": "List of profile views for the current page" + }, + "totalItems": { + "type": "integer", + "title": "Total Items", + "description": "Total number of items in the list" + }, + "pageNumber": { + "type": "integer", + "title": "Page Number", + "description": "Page number" + }, + "pageSize": { + "type": "integer", + "title": "Page Size", + "description": "Number of items per page" + } + }, + "type": "object", + "required": [ + "items", + "totalItems", + "pageNumber", + "pageSize" + ], + "title": "ProfileListResponse", + "description": "Response model for paginated profile list requests." + }, + "ProfileNotFoundError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Profile not found" + } + }, + "type": "object", + "title": "ProfileNotFoundError", + "description": "Error response when a profile is not found" + }, + "ProfileUpdateRequest": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the profile" + }, + "userId": { + "anyOf": [ + { + "type": "string", + "maxLength": 255 + }, + { + "type": "null" + } + ], + "title": "User ID", + "description": "Your internal user identifier for this profile. Use this to associate a profile with a user in your system." + } + }, + "type": "object", + "title": "ProfileUpdateRequest", + "description": "Request model for updating a profile." + }, + "ProfileView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "ID", + "description": "Unique identifier for the profile" + }, + "userId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User ID", + "description": "Your internal user identifier for this profile. Use this to associate a profile with a user in your system." + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the profile" + }, + "lastUsedAt": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Last Used At", + "description": "Timestamp when the profile was last used" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "Timestamp when the profile was created" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "Timestamp when the profile was last updated" + }, + "cookieDomains": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Cookie Domains", + "description": "List of domain URLs that have cookies stored for this profile" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt" + ], + "title": "ProfileView", + "description": "View model for representing a profile. A profile lets you preserve the login state between sessions.\n\nWe recommend that you create a separate profile for each user of your app.\nYou can assign a user_id to each profile to easily identify which user the profile belongs to." + }, + "ProxyCountryCode": { + "type": "string", + "enum": [ + "ad", + "ae", + "af", + "ag", + "ai", + "al", + "am", + "an", + "ao", + "aq", + "ar", + "as", + "at", + "au", + "aw", + "az", + "ba", + "bb", + "bd", + "be", + "bf", + "bg", + "bh", + "bi", + "bj", + "bl", + "bm", + "bn", + "bo", + "bq", + "br", + "bs", + "bt", + "bv", + "bw", + "by", + "bz", + "ca", + "cc", + "cd", + "cf", + "cg", + "ch", + "ck", + "cl", + "cm", + "co", + "cr", + "cs", + "cu", + "cv", + "cw", + "cx", + "cy", + "cz", + "de", + "dj", + "dk", + "dm", + "do", + "dz", + "ec", + "ee", + "eg", + "eh", + "er", + "es", + "et", + "fi", + "fj", + "fk", + "fm", + "fo", + "fr", + "ga", + "gd", + "ge", + "gf", + "gg", + "gh", + "gi", + "gl", + "gm", + "gn", + "gp", + "gq", + "gr", + "gs", + "gt", + "gu", + "gw", + "gy", + "hk", + "hm", + "hn", + "hr", + "ht", + "hu", + "id", + "ie", + "il", + "im", + "in", + "iq", + "ir", + "is", + "it", + "je", + "jm", + "jo", + "jp", + "ke", + "kg", + "kh", + "ki", + "km", + "kn", + "kp", + "kr", + "kw", + "ky", + "kz", + "la", + "lb", + "lc", + "li", + "lk", + "lr", + "ls", + "lt", + "lu", + "lv", + "ly", + "ma", + "mc", + "md", + "me", + "mf", + "mg", + "mh", + "mk", + "ml", + "mm", + "mn", + "mo", + "mp", + "mq", + "mr", + "ms", + "mt", + "mu", + "mv", + "mw", + "mx", + "my", + "mz", + "na", + "nc", + "ne", + "nf", + "ng", + "ni", + "nl", + "no", + "np", + "nr", + "nu", + "nz", + "om", + "pa", + "pe", + "pf", + "pg", + "ph", + "pk", + "pl", + "pm", + "pn", + "pr", + "ps", + "pt", + "pw", + "py", + "qa", + "re", + "ro", + "rs", + "ru", + "rw", + "sa", + "sb", + "sc", + "sd", + "se", + "sg", + "sh", + "si", + "sj", + "sk", + "sl", + "sm", + "sn", + "so", + "sr", + "ss", + "st", + "sv", + "sx", + "sy", + "sz", + "tc", + "td", + "tf", + "tg", + "th", + "tj", + "tk", + "tl", + "tm", + "tn", + "to", + "tr", + "tt", + "tv", + "tw", + "tz", + "ua", + "ug", + "uk", + "us", + "uy", + "uz", + "va", + "vc", + "ve", + "vg", + "vi", + "vn", + "vu", + "wf", + "ws", + "xk", + "ye", + "yt", + "za", + "zm", + "zw" + ], + "title": "ProxyCountryCode" + }, + "RunTaskRequest": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "RunTaskRequest" + }, + "SessionListResponse": { + "properties": { + "sessions": { + "items": { + "$ref": "#/components/schemas/SessionResponse" + }, + "type": "array", + "title": "Sessions", + "description": "List of sessions." + }, + "total": { + "type": "integer", + "title": "Total", + "description": "Total number of sessions matching the query." + }, + "page": { + "type": "integer", + "title": "Page", + "description": "Current page number (1-indexed)." + }, + "pageSize": { + "type": "integer", + "title": "Pagesize", + "description": "Number of sessions per page." + } + }, + "type": "object", + "required": [ + "sessions", + "total", + "page", + "pageSize" + ], + "title": "SessionListResponse" + }, + "SessionNotFoundError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Session not found" + } + }, + "type": "object", + "title": "SessionNotFoundError", + "description": "Error response when a session is not found" + }, + "SessionResponse": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id", + "description": "Unique session identifier." + }, + "status": { + "$ref": "#/components/schemas/BuAgentSessionStatus", + "description": "Current session lifecycle status. Progresses through: `created` (sandbox starting) → `idle` (ready, waiting for task) → `running` (task executing) → `stopped` / `timed_out` / `error`. Poll this field to track progress." + }, + "model": { + "$ref": "#/components/schemas/BuModel", + "description": "The model tier used for this session." + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title", + "description": "Auto-generated short title summarizing the task. Available after the task starts running." + }, + "output": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Output", + "description": "The agent's final output. If `codeMode` was true, this will be an object with `text` (summary), `code` (Python source), and optionally `output` (execution result). If `outputSchema` was provided, this will be structured data conforming to that schema. Otherwise it may be a free-form string or null." + }, + "outputSchema": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Outputschema", + "description": "The JSON Schema that was requested for structured output, if any." + }, + "stepCount": { + "type": "integer", + "title": "Stepcount", + "description": "Number of steps the agent has executed so far.", + "default": 0 + }, + "lastStepSummary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Laststepsummary", + "description": "Human-readable summary of the most recent agent step (e.g. \"Clicking the Submit button\"). Useful for showing real-time progress." + }, + "isTaskSuccessful": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Istasksuccessful", + "description": "Whether the task completed successfully. `true` if the agent achieved the goal, `false` if it failed or gave up, `null` if the task is still running or no task was dispatched." + }, + "liveUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Liveurl", + "description": "URL to view the live browser session. Available immediately on session creation — can be embedded in an iframe to show the browser in real time." + }, + "recordingUrls": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Recordingurls", + "description": "URLs to download session recordings. Only populated if `enableRecording` was set to true and the task has completed.", + "default": [] + }, + "profileId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Profileid", + "description": "ID of the browser profile loaded in this session, if any." + }, + "workspaceId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Workspaceid", + "description": "ID of the workspace attached to this session, if any." + }, + "proxyCountryCode": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProxyCountryCode" + }, + { + "type": "null" + } + ], + "description": "Country code of the proxy used for this session, or null if no proxy." + }, + "maxCostUsd": { + "anyOf": [ + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + }, + { + "type": "null" + } + ], + "title": "Maxcostusd", + "description": "Maximum cost limit in USD set for this session." + }, + "totalInputTokens": { + "type": "integer", + "title": "Totalinputtokens", + "description": "Total LLM input tokens consumed by this session.", + "default": 0 + }, + "totalOutputTokens": { + "type": "integer", + "title": "Totaloutputtokens", + "description": "Total LLM output tokens consumed by this session.", + "default": 0 + }, + "proxyUsedMb": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxyusedmb", + "description": "Proxy bandwidth used in megabytes.", + "default": "0" + }, + "llmCostUsd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Llmcostusd", + "description": "Cost of LLM usage in USD.", + "default": "0" + }, + "proxyCostUsd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Proxycostusd", + "description": "Cost of proxy bandwidth in USD.", + "default": "0" + }, + "browserCostUsd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Browsercostusd", + "description": "Cost of browser compute time in USD.", + "default": "0" + }, + "totalCostUsd": { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$", + "title": "Totalcostusd", + "description": "Total session cost in USD (LLM + proxy + browser).", + "default": "0" + }, + "screenshotUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Screenshoturl", + "description": "URL of the latest browser screenshot. This is a presigned URL that expires after 5 minutes. A new URL is generated each time you fetch the session." + }, + "agentmailEmail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Agentmailemail", + "description": "Temporary email address provisioned for this session (via AgentMail). Only present if `agentmail` was enabled." + }, + "integrationsUsed": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Integrationsused", + "description": "List of integration providers used during this session (e.g. [\"gmail\", \"slack\", \"agentmail\"])." + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Createdat", + "description": "When the session was created." + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updatedat", + "description": "When the session was last updated." + } + }, + "type": "object", + "required": [ + "id", + "status", + "model", + "createdAt", + "updatedAt" + ], + "title": "SessionResponse", + "description": "Represents a session and its current state.\n\nPoll this endpoint to track task progress. The `status` field indicates the session lifecycle stage,\nand `output` contains the agent's structured result once the task completes." + }, + "SessionTimeoutLimitExceededError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Maximum session timeout is 4 hours (240 minutes)." + } + }, + "type": "object", + "title": "SessionTimeoutLimitExceededError", + "description": "Error response when session timeout exceeds the maximum allowed limit" + }, + "ShellResponse": { + "properties": { + "url": { + "type": "string", + "title": "Url" + }, + "expires_in_seconds": { + "type": "integer", + "title": "Expires In Seconds", + "default": 900 + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "ShellResponse", + "description": "Terminal URL — user opens this in their browser, gets a live shell." + }, + "StopSessionRequest": { + "properties": { + "strategy": { + "$ref": "#/components/schemas/StopStrategy", + "description": "How to stop the session. Use \"task\" to stop only the current task and keep the session alive, or \"session\" to destroy the sandbox entirely.", + "default": "session" + } + }, + "type": "object", + "title": "StopSessionRequest" + }, + "StopStrategy": { + "type": "string", + "enum": [ + "task", + "session" + ], + "title": "StopStrategy", + "description": "Strategy for stopping a session.\n\n- `task`: Stop the currently running task but keep the session alive in idle state. You can dispatch another task to the same session afterwards.\n- `session`: Stop the session entirely and destroy the sandbox. The session cannot be reused after this." + }, + "TelegramInstallRequest": { + "properties": { + "bot_token": { + "type": "string", + "title": "Bot Token" + } + }, + "type": "object", + "required": [ + "bot_token" + ], + "title": "TelegramInstallRequest" + }, + "TelegramInstallResponse": { + "properties": { + "installed": { + "type": "boolean", + "title": "Installed" + }, + "bot_username": { + "type": "string", + "title": "Bot Username" + }, + "deeplink": { + "type": "string", + "title": "Deeplink" + }, + "setup_token": { + "type": "string", + "title": "Setup Token" + } + }, + "type": "object", + "required": [ + "installed", + "bot_username", + "deeplink", + "setup_token" + ], + "title": "TelegramInstallResponse" + }, + "TgAutoSessionView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "state": { + "type": "string", + "enum": [ + "pending_browser", + "waiting_scan", + "login_detected", + "minting", + "installing", + "installed", + "failed", + "expired" + ], + "title": "State" + }, + "live_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Live Url" + }, + "bot_username": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Bot Username" + }, + "error_code": { + "anyOf": [ + { + "type": "string", + "enum": [ + "bu_cloud_auth", + "bu_cloud_unavailable", + "bu_cloud_rate_limited", + "bu_cloud_timeout", + "scan_timeout", + "agent_dispatch_failed", + "agent_timeout", + "agent_no_output", + "agent_failed", + "invalid_token", + "bot_username_collision", + "rate_limited_botfather", + "box_not_ready", + "tg_already_installed", + "install_telegram_failed", + "box_offline", + "cancelled", + "concurrent_limit", + "internal" + ] + }, + { + "type": "null" + } + ], + "title": "Error Code" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "state", + "live_url", + "bot_username", + "error_code", + "error_message", + "created_at", + "updated_at" + ], + "title": "TgAutoSessionView", + "description": "Snapshot of a QR onboarding session. Returned by start, status, and\ncancel — same shape so the FE renders a single view component." + }, + "TgAutoStartResponse": { + "properties": { + "session": { + "$ref": "#/components/schemas/TgAutoSessionView" + } + }, + "type": "object", + "required": [ + "session" + ], + "title": "TgAutoStartResponse", + "description": "Returned by POST /me/tg/auto. The session is already running by the\ntime this returns — frontend opens the iframe and starts polling." + }, + "TooManyConcurrentActiveSessionsError": { + "properties": { + "detail": { + "type": "string", + "title": "Detail", + "default": "Too many concurrent active sessions. Please wait for one to finish, kill one, or upgrade your plan." + } + }, + "type": "object", + "title": "TooManyConcurrentActiveSessionsError", + "description": "Error response when user has too many concurrent active sessions" + }, + "TrialEligibilityView": { + "properties": { + "eligible": { + "type": "boolean", + "title": "Eligible" + }, + "reason": { + "anyOf": [ + { + "type": "string", + "enum": [ + "already_used", + "no_owner" + ] + }, + { + "type": "null" + } + ], + "title": "Reason" + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message" + } + }, + "type": "object", + "required": [ + "eligible" + ], + "title": "TrialEligibilityView", + "description": "GET /me/trial-eligibility — is this user eligible for a free trial?\n\nAccount-scoped — paid users see this too (one free trial per user,\nregardless of payment tier).\n\n`reason` is a stable machine-readable code the FE branches on:\n - 'already_used' — user has already started a trial in their\n lifetime. Render an \"upgrade to deploy\" CTA.\n - 'no_owner' — couldn't resolve the project's owner profile (rare,\n older projects). FE falls back to generic \"add credits\" copy.\n - None when eligible=True." + }, + "UpdateBrowserSessionRequest": { + "properties": { + "action": { + "$ref": "#/components/schemas/BrowserSessionUpdateAction", + "title": "Action", + "description": "The action to perform on the session" + } + }, + "type": "object", + "required": [ + "action" + ], + "title": "UpdateBrowserSessionRequest", + "description": "Request model for updating browser session state." + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "WindowCreateRequest": { + "properties": { + "label": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Label" + } + }, + "type": "object", + "title": "WindowCreateRequest" + }, + "WindowListResponse": { + "properties": { + "windows": { + "items": { + "$ref": "#/components/schemas/WindowView" + }, + "type": "array", + "title": "Windows" + } + }, + "type": "object", + "required": [ + "windows" + ], + "title": "WindowListResponse" + }, + "WindowRenameRequest": { + "properties": { + "label": { + "type": "string", + "title": "Label" + } + }, + "type": "object", + "required": [ + "label" + ], + "title": "WindowRenameRequest" + }, + "WindowView": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "label": { + "type": "string", + "title": "Label", + "default": "" + }, + "attached": { + "type": "boolean", + "title": "Attached", + "default": false + }, + "created_at": { + "type": "integer", + "title": "Created At", + "default": 0 + } + }, + "type": "object", + "required": [ + "id" + ], + "title": "WindowView", + "description": "One tmux window on the box. id is the tmux session name (`bux-w`).\n\n`label` is a user-supplied name (max 64 chars) stored as a tmux\nper-session option (@bux-label). `attached` is True if any tmux\nclient is currently connected to the window. The FE shows a small\nindicator next to attached windows so users know which one a phone\nis currently mirroring." + }, + "WorkspaceCreateRequest": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the workspace" + } + }, + "type": "object", + "title": "WorkspaceCreateRequest", + "description": "Request model for creating a new workspace." + }, + "WorkspaceListResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/WorkspaceView" + }, + "type": "array", + "title": "Items", + "description": "List of workspace views for the current page" + }, + "totalItems": { + "type": "integer", + "title": "Total Items", + "description": "Total number of items in the list" + }, + "pageNumber": { + "type": "integer", + "title": "Page Number", + "description": "Page number" + }, + "pageSize": { + "type": "integer", + "title": "Page Size", + "description": "Number of items per page" + } + }, + "type": "object", + "required": [ + "items", + "totalItems", + "pageNumber", + "pageSize" + ], + "title": "WorkspaceListResponse", + "description": "Response model for paginated workspace list requests." + }, + "WorkspaceUpdateRequest": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string", + "maxLength": 100 + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the workspace" + } + }, + "type": "object", + "title": "WorkspaceUpdateRequest", + "description": "Request model for updating a workspace." + }, + "WorkspaceView": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "ID", + "description": "Unique identifier for the workspace" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Optional name for the workspace" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "Timestamp when the workspace was created" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "Timestamp when the workspace was last updated" + } + }, + "type": "object", + "required": [ + "id", + "createdAt", + "updatedAt" + ], + "title": "WorkspaceView", + "description": "View model for a workspace — persistent shared storage across sessions." + }, + "app__endpoints__api__v3__sessions__views__RunTaskRequest": { + "properties": { + "task": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task", + "description": "The natural-language instruction for the agent to execute (e.g. \"Go to amazon.com and find the best-rated wireless mouse under $50\"). Required when dispatching to an existing session." + }, + "model": { + "$ref": "#/components/schemas/BuModel", + "description": "The model to use. \"gemini-3-flash\" is fast and cheap, \"claude-sonnet-4.6\" is balanced, \"claude-opus-4.7\" is most capable (default). See BuModel for details.", + "default": "claude-opus-4.7" + }, + "sessionId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Sessionid", + "description": "ID of an existing idle session to dispatch the task to. If omitted, a new session is created." + }, + "keepAlive": { + "type": "boolean", + "title": "Keepalive", + "description": "If true, the session stays alive in idle state after the task completes instead of automatically stopping. This lets you dispatch follow-up tasks to the same session, preserving browser state and files.", + "default": false + }, + "maxCostUsd": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$" + }, + { + "type": "null" + } + ], + "title": "Maxcostusd", + "description": "Maximum total cost in USD allowed for this session. The task will be stopped if this limit is reached. If omitted, a default limit applies (capped by your available balance). When dispatching a follow-up task to an existing session (`sessionId` is set), supplying this value overrides the session's budget for the upcoming dispatch; otherwise the budget is automatically refreshed to current spend + default." + }, + "profileId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Profileid", + "description": "ID of a browser profile to load into the session. Profiles persist cookies, local storage, and other browser state across sessions. Create profiles via the Profiles API." + }, + "workspaceId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Workspaceid", + "description": "ID of a workspace to attach to the session. Workspaces provide persistent file storage that carries across sessions. Create workspaces via the Workspaces API." + }, + "proxyCountryCode": { + "anyOf": [ + { + "$ref": "#/components/schemas/ProxyCountryCode" + }, + { + "type": "null" + } + ], + "description": "Country code for the browser proxy (e.g. \"US\", \"DE\", \"JP\"). Set to null to disable the proxy. The proxy routes browser traffic through the specified country, useful for accessing geo-restricted content.", + "default": "us" + }, + "outputSchema": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Outputschema", + "description": "A JSON Schema that the agent's final output must conform to. When set, the agent will return structured data matching this schema in the `output` field of the response. Example: {\"type\": \"object\", \"properties\": {\"price\": {\"type\": \"number\"}, \"title\": {\"type\": \"string\"}}}." + }, + "enableScheduledTasks": { + "type": "boolean", + "title": "Enablescheduledtasks", + "description": "If true, the agent can create scheduled tasks that run on a recurring basis (e.g. \"every Monday morning, check my inbox and summarize new emails\"). Scheduled tasks are tied to your project and persist beyond the session. Note: all scheduled tasks are visible project-wide, so avoid enabling this in multi-user setups where task isolation is needed.", + "default": false + }, + "sensitiveData": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Sensitivedata", + "description": "Key-value pairs of sensitive data (e.g. passwords, API keys) that the agent can use via secure placeholders. Keys are exposed to the LLM; values are never shown. The agent uses `key` placeholders in browser_type_text to securely enter values." + }, + "enableRecording": { + "type": "boolean", + "title": "Enablerecording", + "description": "If true, records a video of the browser session. The recording URLs will be available in the `recordingUrls` field of the session response after the task completes.", + "default": false + }, + "skills": { + "type": "boolean", + "title": "Skills", + "description": "If true, enables built-in agent skills like Google Sheets integration and file management. Set to false to restrict the agent to browser-only actions.", + "default": true + }, + "agentmail": { + "type": "boolean", + "title": "Agentmail", + "description": "If true, provisions a temporary email inbox (via AgentMail) for the session. The email address is available in the `agentmailEmail` field of the session response. Useful for tasks that require email verification or sign-ups.", + "default": true + }, + "codeMode": { + "type": "boolean", + "title": "Codemode", + "description": "When true, the agent returns structured output with `text` (summary) and `code` (validated Python source) fields instead of free-form text.", + "default": false + }, + "cacheScript": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Cachescript", + "description": "Controls deterministic script caching. `null` (default): auto-detected — enabled when the task contains `@{{value}}` brackets and a workspace is attached. `true`: force-enable script caching even without brackets (caches the exact task). `false`: force-disable, even if brackets are present. When active, the first call runs the full agent and saves a reusable script. Subsequent calls with the same task template execute the cached script with $0 LLM cost. Requires workspace_id when enabled. Example: \"Get prices from @{{https://example.com}} for @{{electronics}}\"." + }, + "useOwnKey": { + "type": "boolean", + "title": "Useownkey", + "description": "If true, uses your own LLM API key (configured in project settings) instead of Browser Use managed keys. You pay your provider directly for inference; Browser Use charges a reduced orchestration fee (0.2× of provider list prices). If no key is configured for the model's provider, the request is rejected.", + "default": false + }, + "autoHeal": { + "type": "boolean", + "title": "Autoheal", + "description": "When cache_script is active, controls whether a lightweight LLM validates the cached script output. If the output looks incorrect (empty, error, wrong structure), the system automatically re-triggers the full agent to generate a new version of the script. Set to false to disable validation and always return the raw script output.", + "default": true + } + }, + "type": "object", + "title": "RunTaskRequest", + "description": "Create a new session, dispatch a task, or both.\n\n- **No `sessionId` + no `task`**: creates an idle session (useful for uploading files before running a task).\n- **No `sessionId` + `task`**: creates a new session and immediately runs the task.\n- **`sessionId` + `task`**: dispatches the task to an existing idle session.\n- **`sessionId` + no `task`**: returns 422 — a task is required when targeting an existing session." + }, + "SearchRequest": { + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "type": "string", + "minLength": 1, + "description": "The search query in natural language. Returns a ranked set of relevant web results.", + "example": "latest research on protein folding" + } + } + }, + "SearchResult": { + "type": "object", + "required": [ + "url", + "content" + ], + "properties": { + "title": { + "type": "string", + "description": "Title of the source. Omitted when the source does not provide one.", + "example": "An Introduction to Protein Folding" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Canonical URL of the source.", + "example": "https://example.com/protein-folding" + }, + "published_date": { + "type": "string", + "description": "Publication date of the source (YYYY-MM-DD). Omitted when the source does not provide one.", + "example": "2026-04-08" + }, + "content": { + "type": "string", + "description": "Relevant excerpts from the source, joined into a single markdown string and optimized for LLM consumption.", + "example": "# An Introduction to Protein Folding\n\n..." + } + } + }, + "SearchResponse": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "description": "Ranked list of web results, most relevant first.", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + } + } + }, + "SearchError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "description": "A generic, provider-agnostic error message.", + "example": "insufficient balance" + } + } + }, + "FetchRetryConfig": { + "type": "object", + "description": "Retry behavior for failed requests.", + "properties": { + "count": { + "type": "integer", + "description": "Number of retry attempts.", + "default": 3 + }, + "on_status": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Status codes that trigger a retry.", + "default": [ + 500, + 502, + 503, + 504 + ] + }, + "backoff_ms": { + "type": "integer", + "description": "Initial backoff in milliseconds (exponential).", + "default": 100 + } + } + }, + "FetchRequest": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Target URL.", + "example": "https://example.com" + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS" + ], + "default": "GET", + "description": "HTTP method." + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Additional HTTP headers." + }, + "body": { + "type": "string", + "description": "Request body as text." + }, + "body_base64": { + "type": "string", + "description": "Request body as base64, for binary content." + }, + "content_type": { + "type": "string", + "description": "Override the Content-Type header." + }, + "output_format": { + "type": "string", + "enum": [ + "raw", + "markdown", + "structured", + "simplified" + ], + "default": "raw", + "description": "How to format the response body. `raw` returns the page unchanged; `markdown` returns clean readable text; `structured` returns a parsed object (title, links, headings, tables); `simplified` strips boilerplate." + }, + "follow_redirects": { + "type": "boolean", + "default": true, + "description": "Follow HTTP redirects." + }, + "max_redirects": { + "type": "integer", + "default": 10, + "description": "Maximum redirects to follow." + }, + "timeout_ms": { + "type": "integer", + "default": 30000, + "maximum": 120000, + "description": "Request timeout in milliseconds." + }, + "session_id": { + "type": "string", + "description": "Session ID. Requests sharing a session persist cookies and the proxy IP across calls." + }, + "proxy_country": { + "type": "string", + "default": "US", + "description": "ISO 3166-1 alpha-2 country code for proxy routing (e.g. `DE`)." + }, + "retry": { + "$ref": "#/components/schemas/FetchRetryConfig" + }, + "insecure_skip_verify": { + "type": "boolean", + "default": false, + "description": "Skip TLS certificate verification." + } + } + }, + "FetchResponse": { + "type": "object", + "properties": { + "status_code": { + "type": "integer", + "description": "HTTP status code." + }, + "status": { + "type": "string", + "description": "Full status string (e.g. \"200 OK\")." + }, + "headers": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Response headers. Each value is a list of strings." + }, + "body": { + "type": "string", + "description": "Response body as text." + }, + "body_base64": { + "type": "string", + "description": "Response body as base64, when binary." + }, + "is_binary": { + "type": "boolean", + "description": "Whether the response is binary content." + }, + "final_url": { + "type": "string", + "description": "Final URL after redirects." + }, + "redirect_count": { + "type": "integer", + "description": "Number of redirects followed." + }, + "protocol": { + "type": "string", + "description": "HTTP protocol version (e.g. \"HTTP/2.0\")." + }, + "error": { + "type": "string", + "description": "Error message if the request failed." + } + } + }, + "FetchError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "description": "Error message." + }, + "code": { + "type": "integer", + "description": "HTTP status code." + }, + "details": { + "type": "string", + "description": "Additional error details." + } + } + } + }, + "securitySchemes": { + "APIKeyHeader": { + "type": "apiKey", + "in": "header", + "name": "X-Browser-Use-API-Key" + } + } + } +} From 22e6a1ddd99a0a7843e4bba3badc6683af9cbe9a Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 22:34:01 -0700 Subject: [PATCH 05/13] docs: restructure Cloud navigation and add browser + agent pages - Split Playwright/Puppeteer/Selenium into per-framework pages - Add overview, create, sessions, screenshots, pricing, performance, and OSS-vs-cloud pages - Journey-order Browser nav; split Platform features from Integrations; rename to Authentication group; retitle Profiles / Cookie sync - Claude logo on Claude Code and Claude Managed Agents --- docs/cloud/agent/overview.mdx | 35 ++++++ docs/cloud/agent/performance.mdx | 55 ++++++++ docs/cloud/browser/captcha.mdx | 47 +++++++ docs/cloud/browser/create.mdx | 99 +++++++++++++++ docs/cloud/browser/open-source-agent.mdx | 56 +++++++++ docs/cloud/browser/overview.mdx | 35 ++++++ ...-puppeteer-selenium.mdx => playwright.mdx} | 113 ++++++++--------- docs/cloud/browser/puppeteer.mdx | 117 ++++++++++++++++++ docs/cloud/browser/screenshots.mdx | 73 +++++++++++ docs/cloud/browser/selenium.mdx | 68 ++++++++++ docs/cloud/browser/sessions.mdx | 82 ++++++++++++ docs/cloud/guides/profile-sync.mdx | 4 +- docs/cloud/introduction.mdx | 44 +++++++ docs/cloud/open-source-vs-cloud.mdx | 34 +++++ docs/cloud/pricing.mdx | 71 +++++++++++ .../tutorials/integrations/claude-code.mdx | 1 + .../integrations/claude-managed-agents.mdx | 1 + .../tutorials/integrations/playwright.mdx | 35 ------ docs/docs.json | 89 +++++++++---- 19 files changed, 939 insertions(+), 120 deletions(-) create mode 100644 docs/cloud/agent/overview.mdx create mode 100644 docs/cloud/agent/performance.mdx create mode 100644 docs/cloud/browser/captcha.mdx create mode 100644 docs/cloud/browser/create.mdx create mode 100644 docs/cloud/browser/open-source-agent.mdx create mode 100644 docs/cloud/browser/overview.mdx rename docs/cloud/browser/{playwright-puppeteer-selenium.mdx => playwright.mdx} (50%) create mode 100644 docs/cloud/browser/puppeteer.mdx create mode 100644 docs/cloud/browser/screenshots.mdx create mode 100644 docs/cloud/browser/selenium.mdx create mode 100644 docs/cloud/browser/sessions.mdx create mode 100644 docs/cloud/introduction.mdx create mode 100644 docs/cloud/open-source-vs-cloud.mdx create mode 100644 docs/cloud/pricing.mdx delete mode 100644 docs/cloud/tutorials/integrations/playwright.mdx diff --git a/docs/cloud/agent/overview.mdx b/docs/cloud/agent/overview.mdx new file mode 100644 index 00000000..309c63b6 --- /dev/null +++ b/docs/cloud/agent/overview.mdx @@ -0,0 +1,35 @@ +--- +title: Overview +description: "The hosted agent takes a task in plain language and drives a stealth browser until it's done." +icon: robot +--- + +The agent is a hosted loop: it reads the page, decides an action, executes it, and repeats until the task is complete. You send a task, you get a result. + +```python +from browser_use_sdk import BrowserUse + +client = BrowserUse() +result = client.run("List the top 5 posts on Hacker News with their points") +print(result.output) +``` + +Each run gets its own [stealth cloud browser](/cloud/browser/stealth) with proxies and CAPTCHA handling already on. No browser management, no selectors, no waiting logic. + +## When to use the agent + +The agent fits tasks where you care about the outcome, not the exact clicks: data extraction from sites that change layout, workflows across several pages, form submission, or anything you'd rather describe than script. If you need pixel-exact control or deterministic repetition, drive a [browser session](/cloud/browser/overview) directly instead, or record an agent run once and replay it with [cache scripts](/cloud/agent/cache-script). + +## What the agent can do + +- [Structured output](/cloud/agent/structured-output) — get results as typed JSON matching your schema +- [Follow-up tasks](/cloud/agent/follow-up-tasks) — continue in the same browser with context intact +- [Streaming](/cloud/agent/streaming) — watch steps as they happen +- [Workspaces](/cloud/agent/workspaces) — files the agent reads and writes during a run +- [Human-in-the-loop](/cloud/agent/human-in-the-loop) — take over the browser mid-task, then hand back +- [Models](/cloud/agent/models) — pick the LLM that drives the loop +- [Cache scripts](/cloud/agent/cache-script) — record a run, replay it without LLM calls + +## Next + +Start with the [agent quickstart](/cloud/agent/quickstart). For latency and cost tuning, see [Performance & speed](/cloud/agent/performance). diff --git a/docs/cloud/agent/performance.mdx b/docs/cloud/agent/performance.mdx new file mode 100644 index 00000000..9a0b4c59 --- /dev/null +++ b/docs/cloud/agent/performance.mdx @@ -0,0 +1,55 @@ +--- +title: Performance & speed +description: "Every setting that affects agent latency, from model choice to typing speed." +icon: gauge-high +--- + +Agent latency has four sources, in descending order of impact: LLM inference per step, number of steps, page-load waits, and input mechanics. Tune them in that order. + +## Model choice + +The model runs on every step, so it dominates end-to-end time. Smaller models cut per-step latency at some cost in reliability on hard pages. See [Models](/cloud/agent/models) for the current lineup and speed characteristics. + +## Fewer, cheaper steps + +- **`use_thinking`** — disables the model's extended reasoning per step. +- **`use_judge`** — the post-task quality evaluator; disabling it saves a final LLM call when you don't need verification. +- **`use_vision`** — controls whether screenshots are sent to the model; text-only steps are faster and cheaper, at the cost of visual grounding. +- **`max_history_items`** — caps how much history is resent each step; smaller history means smaller prompts. + +{/* TEAM REVIEW: confirm which of these parameters are exposed on cloud v3/v4 runs vs library-only, and document defaults per surface. Evals found them documented only in legacy v2 docs — flash_mode was removed as inaccurate, verify the rest are current before publish. */} + +## Waits and page loads + +The agent waits between actions and for pages to settle. In the library these are browser-level settings (`wait_between_actions`, `minimum_wait_page_load_time`, `wait_for_network_idle_page_load_time`); lowering them speeds up action-dense tasks on fast sites and risks acting before slow pages are ready. + +{/* TEAM REVIEW: reconcile the documented default for wait_between_actions (docs said 0.5s, source says 0.1s) and state the correct values here. */} + +## Typing speed + +Text is typed character by character through CDP with a small fixed delay per keystroke. This is deliberate: instant-fill is a bot tell, and human-paced input is part of staying unblocked. If a form fill feels slow, that's the trade-off working. + +{/* TEAM REVIEW: the per-character delay is hardcoded at 5ms in the library source and documented nowhere. Confirm the value, whether cloud uses the same, and whether we want to expose it as a config field (product ticket exists). */} + +## The fast path, all together + +```python +result = await client.run( + "Get the price of iPhone 16 on amazon.de", + use_thinking=False, + use_judge=False, + use_vision=False, +) +``` + +{/* TEAM REVIEW: verify this exact snippet against the current v3/v4 run signature before publish. */} + +## Deterministic replay: skip the LLM entirely + +For workflows you run repeatedly, do the slow run once and replay it: [cache scripts](/cloud/agent/cache-script) re-execute a recorded run without LLM calls, which is faster and cheaper than any tuning above. + +## Further reading + +- [Speed matters: how Browser Use achieves the fastest agent execution](https://browser-use.com/posts/speed-matters) +- [The fastest web agent in the world](https://browser-use.com/posts/llm-gateway) +- [What LLM model should I use for Browser Use?](https://browser-use.com/posts/what-model-to-use) — the speed/accuracy tradeoff per model diff --git a/docs/cloud/browser/captcha.mdx b/docs/cloud/browser/captcha.mdx new file mode 100644 index 00000000..fed6eaee --- /dev/null +++ b/docs/cloud/browser/captcha.mdx @@ -0,0 +1,47 @@ +--- +title: CAPTCHA Solving +description: "Browser Use remote browsers solve CAPTCHAs automatically, on by default, on every plan." +icon: shield-check +--- + +Browser Use remote browsers are state-of-the-art for stealth. By using a custom Chromium fork with dozens of patches, web agents get blocked by CAPTCHAs and anti-bot systems noticeably less on the websites users care about most. Read how we do it in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and see the [benchmark results](https://browser-use.com/benchmarks): 84.8% on BrowserBench and 81% bypass on high-security sites, ahead of every other provider. + +When a CAPTCHA does appear, remote browsers have **automatic CAPTCHA solving** built in. You do not need to configure anything, on the browser or on the attached agent or automation library (Playwright, Puppeteer, Selenium). It is on by default on every plan, including the [free tier](/cloud/pricing). + +## Setting up a stealth browser + +There is nothing to turn on. Stealth and CAPTCHA solving come with every session. Start one: + + + + SDK, REST, or a single WebSocket URL. + + + Playwright, Puppeteer, or Selenium over CDP. + + + What the hardened Chromium fork does. + + + Residential IPs in 195+ countries, on by default. + + + +## FAQ + +**Does the open-source library solve CAPTCHAs?** + +Without remote browsers, [open-source](https://github.com/browser-use/browser-use) agents have no stealth or CAPTCHA solving. Giving your agent stealth is easy: run it on a remote browser with a single parameter. See [Cloud browser + open source agent](/cloud/browser/open-source-agent). + +**Can I use a third-party CAPTCHA solver?** + +No, we do not support third-party CAPTCHA solver plugins on the browser. If your CAPTCHAs are not being solved properly, reach out and we will look into it. + +**Do I need to enable anything for CAPTCHA solving?** + +No. Remote browsers solve CAPTCHAs for you automatically. + +## Further reading + +- [Prove you are a robot: CAPTCHAs for agents](https://browser-use.com/posts/prove-you-are-a-robot) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) diff --git a/docs/cloud/browser/create.mdx b/docs/cloud/browser/create.mdx new file mode 100644 index 00000000..59e0fb03 --- /dev/null +++ b/docs/cloud/browser/create.mdx @@ -0,0 +1,99 @@ +--- +title: Create a browser session +description: "Every way to start a cloud browser: SDK, REST, or a single WebSocket URL, with all parameters and the response schema." +icon: plus +--- + +Three ways to create a session. All of them return a browser with stealth, CAPTCHA solving, and a residential proxy already on. + +## SDK + + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) # connect any CDP client here +print(browser.live_url) # watch the session in a browser tab +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); +console.log(browser.liveUrl); +``` + + +## REST + +```bash +curl -X POST "https://api.browser-use.com/api/v3/browsers" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode": "us", "timeout": 60}' +``` + +## WebSocket URL (no SDK, no create call) + +Connect directly and the session is created for you. Configuration goes in query parameters, and the session stops when the socket disconnects. + +```text +wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us +``` + +## Parameters + +All parameters are optional. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `profileId` | `string` (UUID) | — | Load a saved [profile](/cloud/guides/profile-sync) (cookies, localStorage) into the session. | +| `proxyCountryCode` | `string` | `us` | Residential proxy country. Set to `null` to disable the proxy. | +| `timeout` | `int` | `60` | Session lifetime in minutes, 1–240. The session stops automatically when it expires. | +| `browserScreenWidth` | `int` | — | Screen width in pixels, 320–6144. | +| `browserScreenHeight` | `int` | — | Screen height in pixels, 320–3456. | +| `allowResizing` | `bool` | `false` | Allow window resizing during the session. Not recommended: resizing reduces stealth. | +| `customProxy` | `object` | — | Bring your own proxy instead of ours. | +| `enableRecording` | `bool` | `false` | Record the session. The video is available as `recordingUrl` after the session stops. | + +{/* TEAM REVIEW: the WSS connection path previously documented timeout default as 15 minutes; the v3 API spec says 60. Confirm which is correct per method and align the framework pages. */} + +## Response + +`201` with a browser session object: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-15T21:00:00Z", + "startedAt": "2026-07-15T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in REST and TypeScript (`cdpUrl`, `liveUrl`), snake_case in Python (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable, check them before connecting. + +## Errors + +| Status | Meaning | +|--------|---------| +| `403` | Session timeout limit exceeded for your plan. | +| `404` | The `profileId` doesn't exist. | +| `422` | Invalid parameter value. | +| `429` | Too many concurrent active sessions. Stop unused sessions or raise your limit. | + +## Next + +- Connect with [Playwright](/cloud/browser/playwright), [Puppeteer](/cloud/browser/puppeteer), or [Selenium](/cloud/browser/selenium) +- [Manage the session](/cloud/browser/sessions): lifecycle, stopping, billing diff --git a/docs/cloud/browser/open-source-agent.mdx b/docs/cloud/browser/open-source-agent.mdx new file mode 100644 index 00000000..ee9b8992 --- /dev/null +++ b/docs/cloud/browser/open-source-agent.mdx @@ -0,0 +1,56 @@ +--- +title: Cloud browser + open source agent +description: "Run the open-source Browser Use agent on a cloud stealth browser. Your code, our infrastructure." +icon: plug +--- + +The [open-source library](/open-source/introduction) runs the agent on your machine. By default it also runs the *browser* on your machine, which means no stealth, no residential proxy, and no CAPTCHA solving. This page connects the two: keep your local agent code, point it at a cloud browser. + +## Connect by CDP URL + +Create a cloud browser, then pass its CDP URL to the library's `Browser`: + +```python +import asyncio +from browser_use import Agent, Browser, ChatOpenAI +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse() + cloud_browser = await client.browsers.create(proxy_country_code="us") + + try: + agent = Agent( + task="Find the current price of iPhone 16 on amazon.de", + llm=ChatOpenAI(model="gpt-4o"), + browser=Browser(cdp_url=cloud_browser.cdp_url), + ) + await agent.run() + finally: + await client.browsers.stop(cloud_browser.id) + +asyncio.run(main()) +``` + +The agent behaves exactly as it does locally. The browser it drives is a [stealth Chromium](/cloud/browser/stealth) with [CAPTCHA solving](/cloud/browser/captcha) and a [residential proxy](/cloud/browser/proxies), and you can watch it work through the session's `live_url`. + +{/* TEAM REVIEW: confirm the `use_cloud=True` shorthand on Browser() — parameter name, minimum library version, and whether it should be the primary example instead of the cdp_url form. */} + +## What you get, what you keep + +| | Stays yours | Comes from Cloud | +|---|---|---| +| Agent loop, prompts, custom tools | ✓ | | +| LLM choice and API keys | ✓ | | +| Browser runtime | | ✓ stealth Chromium | +| Proxy / IP | | ✓ residential, 195+ countries | +| CAPTCHA handling | | ✓ automatic | +| Live view and recording | | ✓ per session | + +Billing: only the browser session ($0.02/hour plus proxy data). Your LLM tokens go to your own provider. + +## Related + +- [Create a browser session](/cloud/browser/create) — all session parameters +- [Open source vs Cloud](/cloud/open-source-vs-cloud) — the full decision guide +- [Manage browser sessions](/cloud/browser/sessions) — always stop sessions when done diff --git a/docs/cloud/browser/overview.mdx b/docs/cloud/browser/overview.mdx new file mode 100644 index 00000000..ab3860df --- /dev/null +++ b/docs/cloud/browser/overview.mdx @@ -0,0 +1,35 @@ +--- +title: Overview +description: "Remote stealth browsers you control over CDP. What they are and when to use one." +icon: globe +--- + +A Browser Use cloud browser is a real Chromium instance running on our infrastructure that your code controls remotely over the Chrome DevTools Protocol (CDP). Create one with an API call, get back a `cdpUrl`, and drive it with Playwright, Puppeteer, or any CDP client, the same way you'd drive a local browser. + +The difference from local Chromium is what's built in. Every session runs our [hardened Chromium fork](/cloud/browser/stealth) with anti-fingerprinting patches, [automatic CAPTCHA solving](/cloud/browser/captcha), and a [residential proxy](/cloud/browser/proxies) in your choice of 195+ countries. None of it needs configuration. + +## When to use a cloud browser + +- **Your Playwright/Puppeteer scripts get blocked.** Same code, but running on infrastructure that sites treat as a normal user. +- **You don't want to run browsers.** No Chrome processes, no headless servers, no scaling browser pools. +- **You're building your own agent.** Full CDP access means any framework or custom tooling works. You can also run the [open-source Browser Use agent on a cloud browser](/cloud/browser/open-source-agent). +- **You need a watchable, recordable session.** Every session has a [live view](/cloud/browser/live-preview) you can open or embed, and optional recording. + +If you'd rather describe the task and let AI do the driving, use the [Agent](/cloud/agent/overview) instead. The two combine: agents run inside browser sessions, and you can connect your own code to the browser behind an agent run. + +## How it fits together + +1. [Create a browser session](/cloud/browser/create) — SDK, REST, or a single WebSocket URL +2. Connect your framework — [Playwright](/cloud/browser/playwright), [Puppeteer](/cloud/browser/puppeteer), or [Selenium](/cloud/browser/selenium) +3. Automate as usual — the session behaves like local Chromium with better manners from websites +4. [Manage the session](/cloud/browser/sessions) — timeouts, stopping, what you're billed for + +## Logging into websites + +Sessions start clean by default. To carry login state across sessions, use [profiles / cookie sync](/cloud/guides/profile-sync), [authentication](/cloud/guides/authentication), and [2FA support](/cloud/guides/2fa). + +## Further reading + +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) — how the cloud browser is built +- [Closer to the Metal: Leaving Playwright for CDP](https://browser-use.com/posts/playwright-to-cdp) — why the browser is driven over CDP +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and the [benchmark results](https://browser-use.com/benchmarks) (84.8% BrowserBench, 81% bypass on high-security sites) diff --git a/docs/cloud/browser/playwright-puppeteer-selenium.mdx b/docs/cloud/browser/playwright.mdx similarity index 50% rename from docs/cloud/browser/playwright-puppeteer-selenium.mdx rename to docs/cloud/browser/playwright.mdx index 85134290..7ae50231 100644 --- a/docs/cloud/browser/playwright-puppeteer-selenium.mdx +++ b/docs/cloud/browser/playwright.mdx @@ -1,17 +1,20 @@ --- -title: Playwright, Puppeteer, Selenium -description: "Connect your automation framework to Browser Use's stealth infrastructure via CDP." +title: Playwright +description: "Connect Playwright to a remote stealth browser over CDP — Python and TypeScript." icon: code --- -Every session runs in a [hardened Chromium fork](/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](/cloud/browser/proxies) enabled by default — no configuration needed. +Run your Playwright scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Playwright scripts and want to run them on stealth infrastructure +- You need pixel-perfect control (screenshots, specific click coordinates, form filling) +- You want to combine agent tasks with manual browser automation ## Option 1: WebSocket URL (no SDK) Connect with a single URL. All configuration is passed as query parameters. -### Playwright - ```python Python from playwright.async_api import async_playwright @@ -40,42 +43,7 @@ await browser.close(); ``` -### Puppeteer - -```typescript -import puppeteer from "puppeteer-core"; - -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); -const [page] = await browser.pages(); -await page.goto("https://example.com"); -console.log(await page.title()); -await browser.close(); -``` - -### Selenium - -Selenium requires a local WebSocket proxy to connect to Browser Use's remote CDP endpoint. Use [selenium-wire](https://github.com/wkeeling/selenium-wire) or connect through Playwright's CDP bridge instead: - -```python -from playwright.sync_api import sync_playwright - -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(WSS_URL) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) - browser.close() -``` - - - Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use Playwright or Puppeteer instead. - - -## Query parameters +### Query parameters | Parameter | Type | Description | |-----------|------|-------------| @@ -88,9 +56,7 @@ with sync_playwright() as p: ## Option 2: SDK -Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright +Create a browser via the SDK, get a `cdp_url`, and connect. The SDK also gives you a `live_url` to [watch or embed the session](/cloud/browser/live-preview). ```python Python @@ -130,28 +96,55 @@ await client.browsers.stop(browser.id); ``` -### Puppeteer +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` -```typescript -import { BrowserUse } from "browser-use-sdk/v3"; -import puppeteer from "puppeteer-core"; +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`) and snake_case in the Python SDK (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. -const client = new BrowserUse(); -const browser = await client.browsers.create(); +### Stopping a session over REST -// Puppeteer needs the WebSocket URL from /json/version -const resp = await fetch(`${browser.cdpUrl}/json/version`); -const { webSocketDebuggerUrl } = await resp.json(); +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: -const pwBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); -const [page] = await pwBrowser.pages(); -await page.goto("https://example.com"); -console.log(await page.title()); -await pwBrowser.close(); - -await client.browsers.stop(browser.id); +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' ``` +## Gotchas + + + Use `connect_over_cdp()` / `connectOverCDP()`, **not** `connect()`. Playwright's `connect()` expects a Playwright-protocol server and fails against a CDP endpoint with an opaque `Protocol error (Browser.getVersion)`. + + +- **Reuse the existing context.** The session already has a context and page open — use `browser.contexts[0].pages[0]` instead of `browser.new_context()`, so you keep the stealth fingerprint and any loaded [profile](/cloud/browser/playwright#query-parameters). +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `pw_browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + +## See also + +- [Puppeteer](/cloud/browser/puppeteer) and [Selenium](/cloud/browser/selenium) connections +- [Live preview & recording](/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](/cloud/browser/proxies) and [stealth](/cloud/browser/stealth) configuration diff --git a/docs/cloud/browser/puppeteer.mdx b/docs/cloud/browser/puppeteer.mdx new file mode 100644 index 00000000..5359b006 --- /dev/null +++ b/docs/cloud/browser/puppeteer.mdx @@ -0,0 +1,117 @@ +--- +title: Puppeteer +description: "Connect Puppeteer to a remote stealth browser with browserWSEndpoint." +icon: code +--- + +Run your Puppeteer scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Puppeteer scripts and want to run them on stealth infrastructure +- You want low-level CDP control from Node.js without managing Chrome yourself +- You want to combine agent tasks with manual browser automation + +## Option 1: WebSocket URL (no SDK) + +Connect with a single URL. All configuration is passed as query parameters. + +```typescript +import puppeteer from "puppeteer-core"; + +const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; + +const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); +const [page] = await browser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +await browser.close(); +// Browser is automatically stopped when the WebSocket disconnects +``` + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Option 2: SDK + +Create a browser via the SDK, then resolve the WebSocket endpoint. Unlike Playwright, Puppeteer can't connect to an HTTP CDP URL directly — fetch `/json/version` to get the `webSocketDebuggerUrl` first. + +```typescript +import { BrowserUse } from "browser-use-sdk/v3"; +import puppeteer from "puppeteer-core"; + +const client = new BrowserUse(); +const browser = await client.browsers.create(); + +// Puppeteer needs the WebSocket URL from /json/version +const resp = await fetch(`${browser.cdpUrl}/json/version`); +const { webSocketDebuggerUrl } = await resp.json(); + +const pptrBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); +const [page] = await pptrBrowser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +await pptrBrowser.close(); + +await client.browsers.stop(browser.id); +``` + +The SDK also gives you a `liveUrl` to [watch or embed the session](/cloud/browser/live-preview). + +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. + +### Stopping a session over REST + +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: + +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +## Gotchas + +- **Use `puppeteer-core`.** It's the connect-only package — installing full `puppeteer` downloads a local Chromium you'll never use. +- **`browserWSEndpoint` must be a `ws://`/`wss://` URL.** Passing the SDK's HTTPS `cdpUrl` directly fails; resolve it via `/json/version` as shown above. +- **Viewport.** Puppeteer applies its own 800×600 default viewport after connecting. Pass `defaultViewport: null` to `puppeteer.connect()` to keep the browser's real window size. +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + + +## See also + +- [Playwright](/cloud/browser/playwright) and [Selenium](/cloud/browser/selenium) connections +- [Live preview & recording](/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](/cloud/browser/proxies) and [stealth](/cloud/browser/stealth) configuration diff --git a/docs/cloud/browser/screenshots.mdx b/docs/cloud/browser/screenshots.mdx new file mode 100644 index 00000000..d59ac1f9 --- /dev/null +++ b/docs/cloud/browser/screenshots.mdx @@ -0,0 +1,73 @@ +--- +title: Screenshots +description: "Take viewport and full-page screenshots from a cloud browser session, and control where they're saved." +icon: camera +--- + +A cloud browser session is a normal CDP endpoint, so screenshots work the way your framework takes them, and they save wherever your code runs. + +## Where screenshots are saved + +The most-asked question first: screenshots taken through Playwright or Puppeteer are written by *your* code, to a path *you* choose. Nothing is stored on the session unless you enable [recording](/cloud/browser/sessions#recordings-and-downloads). + + +```python Python +from playwright.async_api import async_playwright +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create() + +async with async_playwright() as p: + pw = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw.contexts[0].pages[0] + await page.goto("https://example.com") + await page.screenshot(path="shots/example.png") # your machine, your path + +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +import { chromium } from "playwright"; +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create(); + +const pw = await chromium.connectOverCDP(browser.cdpUrl); +const page = pw.contexts()[0].pages()[0]; +await page.goto("https://example.com"); +await page.screenshot({ path: "shots/example.png" }); + +await client.browsers.stop(browser.id); +``` + + +## Full page, not just the viewport + +By default a screenshot captures the visible viewport. For the whole page, top to bottom: + +```python +await page.screenshot(path="full.png", full_page=True) +``` + +Playwright stitches the scroll automatically. The result contains page content only, no URL bar or browser chrome, because CDP screenshots capture the rendered page, not the window. + +## Resolution + +Screenshot dimensions follow the browser's screen size, set at [session creation](/cloud/browser/create) with `browserScreenWidth` and `browserScreenHeight` (320–6144 × 320–3456). Set them explicitly if screenshots must match a target resolution: + +```python +browser = await client.browsers.create(browser_screen_width=1920, browser_screen_height=1080) +``` + +{/* TEAM REVIEW: document the default screen size when width/height are omitted, and whether recording resolution (1920x1080 reported by users) can differ from screenshot resolution — a user reported 1512x770 screenshots vs 1920x1080 recordings. */} + +## Screenshots vs recording + +Screenshots are moments; [recording](/cloud/browser/sessions#recordings-and-downloads) is the whole session as video (`enableRecording: true` at create, `recordingUrl` after stop). For debugging agent behavior, recording is usually what you want; for artifacts and QA evidence, screenshots. + +## From agent tasks + +Ask the agent to take screenshots as part of a task and collect them from the run's [workspace files](/cloud/agent/workspaces). + +{/* TEAM REVIEW: add the exact API for retrieving agent step screenshots (the v1 /screenshots endpoint users reference) and note whether those images carry element highlight overlays — users ask for unmarked versions. */} diff --git a/docs/cloud/browser/selenium.mdx b/docs/cloud/browser/selenium.mdx new file mode 100644 index 00000000..683b2938 --- /dev/null +++ b/docs/cloud/browser/selenium.mdx @@ -0,0 +1,68 @@ +--- +title: Selenium +description: "Run Selenium-style automation on Browser Use's stealth browsers — and why to bridge through CDP." +icon: code +--- + +Browser Use's cloud browsers speak Chrome DevTools Protocol (CDP) over a remote WebSocket. Selenium can't consume that natively: its `debugger_address` option only supports local `host:port` connections, not remote `wss://` URLs. + +You have two practical paths. + +## Recommended: bridge through a CDP client + +If you're migrating Selenium scripts, connect through Playwright's sync API — the page-automation model (navigate, locate, click, read) maps one-to-one, and you get the [hardened stealth Chromium](/cloud/browser/stealth) and [residential proxies](/cloud/browser/proxies) with no configuration. + +```python +from playwright.sync_api import sync_playwright + +WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) + browser.close() +# Browser is automatically stopped when the WebSocket disconnects +``` + +Common Selenium → Playwright equivalents: + +| Selenium | Playwright (sync) | +|---|---| +| `driver.get(url)` | `page.goto(url)` | +| `driver.find_element(By.CSS_SELECTOR, s)` | `page.locator(s)` | +| `element.click()` | `page.locator(s).click()` | +| `element.send_keys(text)` | `page.locator(s).fill(text)` | +| `driver.title` | `page.title()` | +| `WebDriverWait(...).until(...)` | built-in auto-waiting | +| `driver.quit()` | `browser.close()` | + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Alternative: keep Selenium with a local proxy + +If you must keep the Selenium API, run a local WebSocket-to-TCP proxy so Chrome's remote debugging endpoint appears as a local `host:port`, e.g. via [selenium-wire](https://github.com/wkeeling/selenium-wire). This adds a moving part we don't manage — for new code, prefer the CDP bridge above. + + + Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use [Playwright](/cloud/browser/playwright) or [Puppeteer](/cloud/browser/puppeteer) instead. + + + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + + +## See also + +- [Playwright](/cloud/browser/playwright) and [Puppeteer](/cloud/browser/puppeteer) connections +- [Live preview & recording](/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](/cloud/browser/proxies) and [stealth](/cloud/browser/stealth) configuration diff --git a/docs/cloud/browser/sessions.mdx b/docs/cloud/browser/sessions.mdx new file mode 100644 index 00000000..4a5dad20 --- /dev/null +++ b/docs/cloud/browser/sessions.mdx @@ -0,0 +1,82 @@ +--- +title: Manage browser sessions +description: "Session lifecycle: states, timeouts, stopping, disconnect behavior, and what you're billed for." +icon: list-check +--- + +A session has two states: `active` and `stopped`. It leaves `active` in exactly three ways: you stop it, its timeout expires, or (WebSocket connections only) the socket disconnects. + +## Stopping a session + +Stopping is an update, not a delete, and it cannot be undone. + + +```python Python +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +await client.browsers.stop(browser.id); +``` +```bash REST +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + + +There is no `POST /browsers/{id}/stop` endpoint. If you're getting a `404` on a stop call, this is why. + +Stop sessions as soon as you're done with them. Browser time is billed at $0.02/hour until the session stops or times out, whichever comes first. + +```python +browser = await client.browsers.create() +try: + ... # your automation +finally: + await client.browsers.stop(browser.id) +``` + +## Disconnecting vs stopping + +The two connection styles behave differently when your client goes away: + +| | Client disconnects | Session keeps running? | +|---|---|---| +| WebSocket URL (`wss://connect.browser-use.com`) | Socket closes | No — the session stops automatically | +| SDK / REST (`browsers.create()` + CDP) | `pw_browser.close()` only detaches your client | Yes — until you call stop or the timeout expires | + +The SDK behavior is what lets you disconnect and reconnect to the same session, but it also means forgotten sessions keep billing. If you see `429 Too many concurrent active sessions`, list and stop the strays: + +```python +sessions = await client.browsers.list(filter_by="active") +for s in sessions.items: + await client.browsers.stop(s.id) +``` + +## Timeouts + +Every session has a lifetime set at creation: `timeout` in minutes, default `60`, maximum `240` (4 hours). The expiry moment comes back as `timeoutAt` in the session object. A timed-out session stops automatically and cannot be extended or reused, so if a workflow might outlive the default, set the timeout up front: + +```python +browser = await client.browsers.create(timeout=240) +``` + +## Inspecting sessions + +```python +browser = await client.browsers.get(session_id) # one session +sessions = await client.browsers.list(page_size=20) # paginated, filter_by="active" | "stopped" +``` + +The session object carries the operational fields: `status`, `timeoutAt`, `startedAt`, `finishedAt`, live and CDP URLs, plus cost tracking (`browserCost`, `proxyCost`, `proxyUsedMb`). + +## Recordings and downloads + +- Create the session with `enableRecording: true` and `recordingUrl` is populated after the session stops. It is `null` while the session runs and shortly after stopping while the video is processed. +- Files downloaded by the browser during the session are listed at `GET /browsers/{session_id}/downloads`. + +## Related + +- [Create a browser session](/cloud/browser/create) — all creation parameters +- [Live preview](/cloud/browser/live-preview) — watch or embed a running session diff --git a/docs/cloud/guides/profile-sync.mdx b/docs/cloud/guides/profile-sync.mdx index b7edc630..99326cf1 100644 --- a/docs/cloud/guides/profile-sync.mdx +++ b/docs/cloud/guides/profile-sync.mdx @@ -1,6 +1,6 @@ --- -title: Sync local and cloud cookies -description: "Sync your local browser cookies to the cloud — instantly authenticate without managing credentials." +title: Profiles / Cookie sync +description: "Profiles carry cookies and login state across sessions — sync them from your local browser or reuse them in the cloud." icon: arrows-rotate --- diff --git a/docs/cloud/introduction.mdx b/docs/cloud/introduction.mdx new file mode 100644 index 00000000..3dd1dbdd --- /dev/null +++ b/docs/cloud/introduction.mdx @@ -0,0 +1,44 @@ +--- +title: Introduction +description: "One API for AI browser agents and stealth cloud browsers. Choose how much control you want." +icon: hand-wave +--- + +Browser Use Cloud gives you two things with one API key: + +1. **An AI agent** that completes web tasks from a plain-language prompt. You describe the outcome, the agent drives the browser. +2. **Stealth cloud browsers** you drive yourself over CDP with Playwright, Puppeteer, or any automation framework. + +Every browser, in both modes, runs a [hardened Chromium fork](/cloud/browser/stealth) with anti-fingerprinting, [automatic CAPTCHA solving](/cloud/browser/captcha), and [residential proxies](/cloud/browser/proxies) enabled by default. + +## Which one do you want? + +**Use the [Agent](/cloud/agent/overview)** when the task is the point and the clicks are not. Price monitoring, form filling, research, multi-step workflows described in a sentence: + +```python +result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +``` + +**Use a [Browser](/cloud/browser/overview)** when you want deterministic control. You have Playwright or Puppeteer scripts already, you need exact selectors and timing, or you're building your own agent: + +```python +browser = await client.browsers.create() +# connect with Playwright over CDP via browser.cdp_url +``` + +The two combine. An agent can run inside a browser session you created, and you can [connect Playwright to the browser behind an agent task](/cloud/browser/playwright) for hybrid workflows: the agent handles fuzzy steps, your code handles deterministic ones. + +## Already using the open-source library? + +The [Browser Use library](/open-source/introduction) runs agents on your machine with your own LLM keys. Cloud adds managed stealth browsers and a hosted agent on top. You can keep your local agent code and point it at a cloud browser, see [Open source vs Cloud](/cloud/open-source-vs-cloud). + +## Start here + +- [Quickstart](/cloud/quickstart) — first task in five minutes +- [Create a browser session](/cloud/browser/create) — every creation method and parameter +- [Pricing](/cloud/pricing) — what costs what + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — accuracy and stealth results vs other providers +- [The ultimate guide to web scraping (2026)](https://browser-use.com/posts/web-scraping-guide-2026) diff --git a/docs/cloud/open-source-vs-cloud.mdx b/docs/cloud/open-source-vs-cloud.mdx new file mode 100644 index 00000000..baca6294 --- /dev/null +++ b/docs/cloud/open-source-vs-cloud.mdx @@ -0,0 +1,34 @@ +--- +title: Open source vs Cloud +description: "The library and the cloud are different products that combine. Here's which one you want." +icon: code-compare +--- + +Browser Use is two things with one name. The confusion is common enough to deserve its own page. + +**The [open-source library](/open-source/introduction)** (`pip install browser-use`) is an agent framework that runs on your machine. You bring your own LLM key, it launches a local Chromium, and nothing leaves your infrastructure. Free, Apache-licensed, yours. + +**Browser Use Cloud** (this documentation) is a paid API with two services: [stealth cloud browsers](/cloud/browser/overview) you can drive with any framework, and a [hosted agent](/cloud/agent/overview) that runs tasks for you, no library install required. + +## Which do you want? + +| You want to... | Use | +|---|---| +| Run an agent locally, free, with your own LLM keys | Open source library | +| Keep your local agent but stop getting blocked by websites | Library + [cloud browser](/cloud/browser/open-source-agent) | +| Drive stealth browsers with existing Playwright/Puppeteer scripts | Cloud [Browser](/cloud/browser/overview) | +| Send a task and get a result, zero infrastructure | Cloud [Agent](/cloud/agent/overview) | + +## Common questions + +**Do I need an API key to use the library?** +No. The library needs an LLM provider key (OpenAI, Anthropic, Ollama for fully local). A Browser Use API key is only needed for cloud features. + +**Does the library have stealth or CAPTCHA solving?** +No. Those are properties of the cloud browsers. The bridge is one parameter: point the library's `Browser` at a [cloud browser session](/cloud/browser/open-source-agent). + +**Is the cloud agent the same agent as the library?** +The cloud agent is the hosted, managed version, with the same task-in, result-out model plus cloud-only features like [structured output](/cloud/agent/structured-output), [workspaces](/cloud/agent/workspaces), and [human-in-the-loop](/cloud/agent/human-in-the-loop). + +**Can I self-host the cloud?** +The library is the self-hosted option: your machines, your browsers, your keys. The stealth browser fleet and hosted agent are not self-hostable. diff --git a/docs/cloud/pricing.mdx b/docs/cloud/pricing.mdx new file mode 100644 index 00000000..443bc67d --- /dev/null +++ b/docs/cloud/pricing.mdx @@ -0,0 +1,71 @@ +--- +title: Pricing & free tier +description: "Browser Use Cloud plans, the free tier (no card required), and every usage-based rate." +icon: credit-card +--- + +Browser Use Cloud has a free tier and four paid plans. Usage (browser time, proxy data, agent tokens) is billed on top of the plan. The [pricing page](https://browser-use.com/pricing) is the canonical source; the numbers here are kept in sync with it. + +## Free tier + +Free, no credit card required. It includes: + +- 3 concurrent browser sessions +- 1 browser profile, 1 team member +- Basic proxy pool +- Advanced [stealth](/cloud/browser/stealth), [CAPTCHA solving](/cloud/browser/captcha), and [webhook events](/cloud/guides/webhooks) +- Community support + +Stealth and CAPTCHA solving are on for every tier, including free. They are not paid add-ons. + +## Plans + +| Plan | Monthly | Included credits | Concurrent sessions | Team members | +|------|---------|------------------|---------------------|--------------| +| Free | $0 | — | 3 | 1 | +| Dev | $29 | $29 | 25 | 5 | +| Business | $299 | $299 | 200 | Unlimited | +| Scaleup | $999 | $999 | 500 | Unlimited | +| Enterprise | Custom | Negotiated | Negotiated | Negotiated | + +Annual billing is pay for 10 months, get 12 (Dev $290/yr, Business $2,990/yr, Scaleup $9,990/yr). + +The concurrent-session limit is what a `429 Too many concurrent active sessions` error refers to. Stop idle sessions or upgrade the plan to raise it. + +## Usage rates + +Billed against your plan credits, then charged if you exceed them. + +**Browser & proxies** +- Browser session: $0.02/hour, active time only +- Proxy bandwidth: $5/GB + +**Agent (v3, token-based at 1.2× provider rates)** + +| Model | Input / output per 1M tokens | +|-------|------------------------------| +| GPT-5.4 Mini | $0.90 / $5.40 | +| Claude Sonnet 4.6 | $3.60 / $18.00 | +| Claude Opus 4.6 / 4.7 | $6.00 / $30.00 | + +When you [bring your own LLM](/cloud/agent/models), those tokens go to your own provider instead. + +**Browser Use Box** (persistent remote agent environment, billed while running) +- Small: $1/day — 2 vCPU, 4 GB RAM, 20 GB +- Medium: $2/day — 2 vCPU, 8 GB RAM, 50 GB +- Large: $4/day — 4 vCPU, 16 GB RAM, 100 GB + +## Tracking spend + +Every browser session object reports its own running cost (`browserCost`, `proxyCost`, `proxyUsedMb`), so spend is inspectable per session. [Stop sessions](/cloud/browser/sessions#stopping-a-session) when done — an idle session bills until it stops or [times out](/cloud/browser/sessions#timeouts). + +## The open-source library is free + +The [library](/open-source/introduction) has no Browser Use charges — you pay only your own LLM provider. Cloud rates apply when you use cloud browsers or the hosted agent. See [Open source vs Cloud](/cloud/open-source-vs-cloud). + +## Further reading + +- [Remote browsers for agents: the Browser Use free tier](https://browser-use.com/posts/free-tier-announcement) +- [How we made cloud browsers 3x cheaper and faster](https://browser-use.com/posts/firecracker-browser-infra) + +{/* TEAM REVIEW: keep this table in sync with browser-use.com/pricing. The v2 agent (per-step from $0.006, task init $0.01) is legacy — omitted here; add a legacy note if v2 users need it. */} diff --git a/docs/cloud/tutorials/integrations/claude-code.mdx b/docs/cloud/tutorials/integrations/claude-code.mdx index 50e025e5..beb7bfd4 100644 --- a/docs/cloud/tutorials/integrations/claude-code.mdx +++ b/docs/cloud/tutorials/integrations/claude-code.mdx @@ -1,6 +1,7 @@ --- title: Claude Code description: Give Claude Code cloud browser automation with Browser Use. +icon: /images/icons/claude.svg --- [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is Anthropic's agentic coding tool that runs in the terminal. Add Browser Use and it gets full cloud browser automation — anti-detect profiles, CAPTCHA solving, residential proxies in 195+ countries, persistent profiles, and stealth browsing. diff --git a/docs/cloud/tutorials/integrations/claude-managed-agents.mdx b/docs/cloud/tutorials/integrations/claude-managed-agents.mdx index 146b7e05..f02dbd04 100644 --- a/docs/cloud/tutorials/integrations/claude-managed-agents.mdx +++ b/docs/cloud/tutorials/integrations/claude-managed-agents.mdx @@ -1,6 +1,7 @@ --- title: Claude Managed Agents description: Give Anthropic's Claude Managed Agents a stealth cloud browser via the Browser Use CLI. +icon: /images/icons/claude.svg --- [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents) run on Anthropic's hosted platform. Install the `browser-use` CLI in the agent's environment and it can drive a stealth cloud browser — with proxies, CAPTCHA solving, live view, and recording. Your API key stays in a credential vault; the model never sees it. diff --git a/docs/cloud/tutorials/integrations/playwright.mdx b/docs/cloud/tutorials/integrations/playwright.mdx deleted file mode 100644 index 353a6643..00000000 --- a/docs/cloud/tutorials/integrations/playwright.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Playwright -description: Connect Playwright to a cloud browser via CDP for full programmatic control. ---- - -Need lower-level control than an agent task? Create a cloud browser and connect directly via Chrome DevTools Protocol (CDP). You get a stealth browser with residential proxy — use it with Playwright, Puppeteer, or any CDP client. - -When to use this: -- You have existing Playwright scripts and want to run them on stealth infrastructure -- You need pixel-perfect control (screenshots, specific click coordinates, form filling) -- You want to combine agent tasks with manual browser automation - -```python -import asyncio -from playwright.async_api import async_playwright -from browser_use_sdk.v3 import AsyncBrowserUse - -async def main(): - client = AsyncBrowserUse() - browser = await client.browsers.create(proxy_country_code="us") - - try: - async with async_playwright() as p: - b = await p.chromium.connect_over_cdp(browser.cdp_url) - page = b.contexts[0].pages[0] - await page.goto("https://example.com") - await page.screenshot(path="screenshot.png") - await b.close() - finally: - await client.browsers.stop(browser.id) - -asyncio.run(main()) -``` - -See [Playwright, Puppeteer, Selenium](/cloud/browser/playwright-puppeteer-selenium) for the full browser API. diff --git a/docs/docs.json b/docs/docs.json index 0fb2e9d8..599efdb8 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -65,7 +65,10 @@ { "group": "Get Started", "pages": [ + "cloud/introduction", + "cloud/open-source-vs-cloud", "cloud/quickstart", + "cloud/pricing", "cloud/vibecoding" ] }, @@ -76,6 +79,7 @@ "group": "Agent", "icon": "robot", "pages": [ + "cloud/agent/overview", "cloud/agent/quickstart", "cloud/agent/models", "cloud/agent/structured-output", @@ -83,17 +87,32 @@ "cloud/agent/streaming", "cloud/agent/workspaces", "cloud/agent/cache-script", - "cloud/agent/human-in-the-loop" + "cloud/agent/human-in-the-loop", + "cloud/agent/performance" ] }, { "group": "Browser", "icon": "globe", "pages": [ + "cloud/browser/overview", + "cloud/browser/create", + "cloud/browser/sessions", "cloud/browser/stealth", + "cloud/browser/captcha", "cloud/browser/proxies", + "cloud/browser/screenshots", "cloud/browser/live-preview", - "cloud/browser/playwright-puppeteer-selenium", + { + "group": "Automation frameworks", + "icon": "code", + "pages": [ + "cloud/browser/playwright", + "cloud/browser/puppeteer", + "cloud/browser/selenium" + ] + }, + "cloud/browser/open-source-agent", { "group": "Authentication", "icon": "key", @@ -110,23 +129,23 @@ { "group": "More", "pages": [ + { + "group": "Platform features", + "icon": "server", + "pages": [ + "cloud/guides/mcp-server", + "cloud/guides/webhooks", + "cloud/guides/x402" + ] + }, { "group": "Integrations", "icon": "plug", "pages": [ - { - "group": "Anthropic", - "icon": "/images/icons/claude.svg", - "pages": [ - "cloud/tutorials/integrations/claude-code", - "cloud/tutorials/integrations/claude-managed-agents" - ] - }, + "cloud/tutorials/integrations/claude-code", + "cloud/tutorials/integrations/claude-managed-agents", "cloud/tutorials/integrations/openclaw", "cloud/tutorials/integrations/hermes-agent", - "cloud/guides/mcp-server", - "cloud/guides/webhooks", - "cloud/guides/x402", "cloud/tutorials/integrations/n8n" ] }, @@ -487,15 +506,35 @@ }, { "source": "/cloud/tutorials/integrations/playwright", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { - "source": "/tutorials/integrations/playwright", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "source": "/cloud/browser/playwright-puppeteer-selenium", + "destination": "/cloud/browser/playwright" }, { - "source": "/cloud/pricing", - "destination": "https://browser-use.com/pricing" + "source": "/cloud/captcha-solving", + "destination": "/cloud/browser/captcha" + }, + { + "source": "/cloud/playwright", + "destination": "/cloud/browser/playwright" + }, + { + "source": "/cloud/connect-playwright", + "destination": "/cloud/browser/playwright" + }, + { + "source": "/cloud/puppeteer", + "destination": "/cloud/browser/puppeteer" + }, + { + "source": "/cloud/selenium", + "destination": "/cloud/browser/selenium" + }, + { + "source": "/tutorials/integrations/playwright", + "destination": "/cloud/browser/playwright" }, { "source": "/cloud/models", @@ -515,7 +554,7 @@ }, { "source": "/cloud/browsers", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { "source": "/quickstart", @@ -907,7 +946,7 @@ }, { "source": "/guides/browser-api", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { "source": "/guides/proxies-and-stealth", @@ -987,7 +1026,7 @@ }, { "source": "/tips/integrations/playwright", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { "source": "/tips/integrations/n8n", @@ -995,7 +1034,7 @@ }, { "source": "/cloud/tips/integrations/playwright", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { "source": "/cloud/tips/integrations/n8n", @@ -1059,7 +1098,7 @@ }, { "source": "/concepts/browser", - "destination": "/cloud/browser/playwright-puppeteer-selenium" + "destination": "/cloud/browser/playwright" }, { "source": "/usage/structured-output", @@ -1104,6 +1143,10 @@ { "source": "/cloud/v1/*", "destination": "/cloud/quickstart" + }, + { + "source": "/cloud/browser/open-source-vs-cloud", + "destination": "/cloud/open-source-vs-cloud" } ] } \ No newline at end of file From 7b3f61987c17ac01a3bb8fea5a1351e0c83d5f17 Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 22:34:01 -0700 Subject: [PATCH 06/13] docs: link blog posts and benchmarks for SEO --- docs/cloud/browser/proxies.mdx | 4 ++++ docs/cloud/browser/stealth.mdx | 9 ++++++++- docs/cloud/guides/authentication.mdx | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/cloud/browser/proxies.mdx b/docs/cloud/browser/proxies.mdx index 6ca1f747..39fedede 100644 --- a/docs/cloud/browser/proxies.mdx +++ b/docs/cloud/browser/proxies.mdx @@ -82,3 +82,7 @@ const browser = await client.browsers.create({ }); ``` + +## Further reading + +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) — how proxy quality affects bypass rates diff --git a/docs/cloud/browser/stealth.mdx b/docs/cloud/browser/stealth.mdx index 288f9d08..cc54c6f3 100644 --- a/docs/cloud/browser/stealth.mdx +++ b/docs/cloud/browser/stealth.mdx @@ -16,4 +16,11 @@ Every cloud browser session runs in a hardened Chromium fork with stealth enable ## Residential proxies -Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. \ No newline at end of file +Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) \ No newline at end of file diff --git a/docs/cloud/guides/authentication.mdx b/docs/cloud/guides/authentication.mdx index 7b5b776c..074c9191 100644 --- a/docs/cloud/guides/authentication.mdx +++ b/docs/cloud/guides/authentication.mdx @@ -96,3 +96,7 @@ await client.profiles.delete(profileId); Profile state is only saved when the session ends. Always call `sessions.stop()` when you are done — if a session is left open or times out, changes may not be persisted. Every code path that uses a profile must stop the session, including error handlers. + +## Further reading + +- [How to authenticate AI web agents](https://browser-use.com/posts/web-agent-authentication) From e9176d613cb26e28f70fc00d3b978ca7345594ec Mon Sep 17 00:00:00 2001 From: Reagan Hsu Date: Tue, 14 Jul 2026 22:44:07 -0700 Subject: [PATCH 07/13] docs: regenerate llms.txt to include new Cloud pages --- docs/cloud/llms-full.txt | 3078 +++++++++++++++++++++++--------- docs/cloud/llms.txt | 95 +- docs/llms-full.txt | 3078 +++++++++++++++++++++++--------- docs/llms.txt | 95 +- docs/open-source/llms-full.txt | 326 +++- docs/open-source/llms.txt | 11 +- 6 files changed, 4881 insertions(+), 1802 deletions(-) diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 12704f34..24d73b42 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -3,6 +3,84 @@ > Machine-readable OpenAPI spec: https://docs.browser-use.com/openapi.json (v3, canonical — also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json). Dashboard: https://cloud.browser-use.com. Create an API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 +# Introduction +Source: https://docs.browser-use.com/cloud/introduction + + +Browser Use Cloud gives you two things with one API key: + +1. **An AI agent** that completes web tasks from a plain-language prompt. You describe the outcome, the agent drives the browser. +2. **Stealth cloud browsers** you drive yourself over CDP with Playwright, Puppeteer, or any automation framework. + +Every browser, in both modes, runs a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with anti-fingerprinting, [automatic CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default. + +## Which one do you want? + +**Use the [Agent](https://docs.browser-use.com/cloud/agent/overview)** when the task is the point and the clicks are not. Price monitoring, form filling, research, multi-step workflows described in a sentence: + +```python +result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +``` + +**Use a [Browser](https://docs.browser-use.com/cloud/browser/overview)** when you want deterministic control. You have Playwright or Puppeteer scripts already, you need exact selectors and timing, or you're building your own agent: + +```python +browser = await client.browsers.create() +# connect with Playwright over CDP via browser.cdp_url +``` + +The two combine. An agent can run inside a browser session you created, and you can [connect Playwright to the browser behind an agent task](https://docs.browser-use.com/cloud/browser/playwright) for hybrid workflows: the agent handles fuzzy steps, your code handles deterministic ones. + +## Already using the open-source library? + +The [Browser Use library](/open-source/introduction) runs agents on your machine with your own LLM keys. Cloud adds managed stealth browsers and a hosted agent on top. You can keep your local agent code and point it at a cloud browser, see [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud). + +## Start here + +- [Quickstart](https://docs.browser-use.com/cloud/quickstart) — first task in five minutes +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — every creation method and parameter +- [Pricing](https://docs.browser-use.com/cloud/pricing) — what costs what + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — accuracy and stealth results vs other providers +- [The ultimate guide to web scraping (2026)](https://browser-use.com/posts/web-scraping-guide-2026) + + +# Open source vs Cloud +Source: https://docs.browser-use.com/cloud/open-source-vs-cloud + + +Browser Use is two things with one name. The confusion is common enough to deserve its own page. + +**The [open-source library](/open-source/introduction)** (`pip install browser-use`) is an agent framework that runs on your machine. You bring your own LLM key, it launches a local Chromium, and nothing leaves your infrastructure. Free, Apache-licensed, yours. + +**Browser Use Cloud** (this documentation) is a paid API with two services: [stealth cloud browsers](https://docs.browser-use.com/cloud/browser/overview) you can drive with any framework, and a [hosted agent](https://docs.browser-use.com/cloud/agent/overview) that runs tasks for you, no library install required. + +## Which do you want? + +| You want to... | Use | +|---|---| +| Run an agent locally, free, with your own LLM keys | Open source library | +| Keep your local agent but stop getting blocked by websites | Library + [cloud browser](https://docs.browser-use.com/cloud/browser/open-source-agent) | +| Drive stealth browsers with existing Playwright/Puppeteer scripts | Cloud [Browser](https://docs.browser-use.com/cloud/browser/overview) | +| Send a task and get a result, zero infrastructure | Cloud [Agent](https://docs.browser-use.com/cloud/agent/overview) | + +## Common questions + +**Do I need an API key to use the library?** +No. The library needs an LLM provider key (OpenAI, Anthropic, Ollama for fully local). A Browser Use API key is only needed for cloud features. + +**Does the library have stealth or CAPTCHA solving?** +No. Those are properties of the cloud browsers. The bridge is one parameter: point the library's `Browser` at a [cloud browser session](https://docs.browser-use.com/cloud/browser/open-source-agent). + +**Is the cloud agent the same agent as the library?** +The cloud agent is the hosted, managed version, with the same task-in, result-out model plus cloud-only features like [structured output](https://docs.browser-use.com/cloud/agent/structured-output), [workspaces](https://docs.browser-use.com/cloud/agent/workspaces), and [human-in-the-loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop). + +**Can I self-host the cloud?** +The library is the self-hosted option: your machines, your browsers, your keys. The stealth browser fleet and hosted agent are not self-hostable. + + # Quick start Source: https://docs.browser-use.com/cloud/quickstart @@ -29,9 +107,9 @@ import asyncio from browser_use_sdk.v3 import AsyncBrowserUse async def main(): -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) + client = AsyncBrowserUse() + result = await client.run("List the top 20 posts on Hacker News today with their points") + print(result.output) asyncio.run(main()) ``` @@ -67,113 +145,122 @@ Want a full working app? Check out the [Chat UI example](https://docs.browser-us If you are an LLM, read/include [docs.browser-use.com/llms-full.txt](https://docs.browser-use.com/llms-full.txt) — it contains the complete SDK reference with all code examples in a single file optimized for LLMs. For a shorter index: [docs.browser-use.com/llms.txt](https://docs.browser-use.com/llms.txt). -# Prompt for Vibecoders -Source: https://docs.browser-use.com/cloud/vibecoding +# Pricing & free tier +Source: https://docs.browser-use.com/cloud/pricing -Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsurf, etc.) — it contains all the context needed to build with Browser Use. +Browser Use Cloud has a free tier and four paid plans. Usage (browser time, proxy data, agent tokens) is billed on top of the plan. The [pricing page](https://browser-use.com/pricing) is the canonical source; the numbers here are kept in sync with it. -``` -https://docs.browser-use.com/cloud/llms.txt -``` +## Free tier +Free, no credit card required. It includes: -# Agent Sign Up for Browser Use -Source: https://docs.browser-use.com/cloud/agent-signup +- 3 concurrent browser sessions +- 1 browser profile, 1 team member +- Basic proxy pool +- Advanced [stealth](https://docs.browser-use.com/cloud/browser/stealth), [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and [webhook events](https://docs.browser-use.com/cloud/guides/webhooks) +- Community support +Stealth and CAPTCHA solving are on for every tier, including free. They are not paid add-ons. -An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. +## Plans -The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. +| Plan | Monthly | Included credits | Concurrent sessions | Team members | +|------|---------|------------------|---------------------|--------------| +| Free | $0 | — | 3 | 1 | +| Dev | $29 | $29 | 25 | 5 | +| Business | $299 | $299 | 200 | Unlimited | +| Scaleup | $999 | $999 | 500 | Unlimited | +| Enterprise | Custom | Negotiated | Negotiated | Negotiated | -## REST flow +Annual billing is pay for 10 months, get 12 (Dev $290/yr, Business $2,990/yr, Scaleup $9,990/yr). -### 1. Request a challenge +The concurrent-session limit is what a `429 Too many concurrent active sessions` error refers to. Stop idle sessions or upgrade the plan to raise it. -```bash -curl -X POST https://api.browser-use.com/cloud/signup \ - -H "Content-Type: application/json" \ - -d '{}' -``` +## Usage rates -Request body, optional (include a user email/name if available): +Billed against your plan credits, then charged if you exceed them. -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` +**Browser & proxies** +- Browser session: $0.02/hour, active time only +- Proxy bandwidth: $5/GB -Response: +**Agent (v3, token-based at 1.2× provider rates)** -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` +| Model | Input / output per 1M tokens | +|-------|------------------------------| +| GPT-5.4 Mini | $0.90 / $5.40 | +| Claude Sonnet 4.6 | $3.60 / $18.00 | +| Claude Opus 4.6 / 4.7 | $6.00 / $30.00 | -### 2. Solve the challenge +When you [bring your own LLM](https://docs.browser-use.com/cloud/agent/models), those tokens go to your own provider instead. -Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. +**Browser Use Box** (persistent remote agent environment, billed while running) +- Small: $1/day — 2 vCPU, 4 GB RAM, 20 GB +- Medium: $2/day — 2 vCPU, 8 GB RAM, 50 GB +- Large: $4/day — 4 vCPU, 16 GB RAM, 100 GB -### 3. Verify the answer +## Tracking spend -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` +Every browser session object reports its own running cost (`browserCost`, `proxyCost`, `proxyUsedMb`), so spend is inspectable per session. [Stop sessions](https://docs.browser-use.com/cloud/browser/sessions#stopping-a-session) when done — an idle session bills until it stops or [times out](https://docs.browser-use.com/cloud/browser/sessions#timeouts). -Request body: +## The open-source library is free -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` +The [library](/open-source/introduction) has no Browser Use charges — you pay only your own LLM provider. Cloud rates apply when you use cloud browsers or the hosted agent. See [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud). -Response: +## Further reading -```json -{ - "api_key": "bu_..." -} -``` +- [Remote browsers for agents: the Browser Use free tier](https://browser-use.com/posts/free-tier-announcement) +- [How we made cloud browsers 3x cheaper and faster](https://browser-use.com/posts/firecracker-browser-infra) -Use the returned key for Browser Use Cloud API requests. +{/* TEAM REVIEW: keep this table in sync with browser-use.com/pricing. The v2 agent (per-step from $0.006, task init $0.01) is legacy — omitted here; add a legacy note if v2 users need it. */} -For example, create a browser session: -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' +# Prompt for Vibecoders +Source: https://docs.browser-use.com/cloud/vibecoding + + +Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsurf, etc.) — it contains all the context needed to build with Browser Use. + +``` +https://docs.browser-use.com/cloud/llms.txt ``` -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). -## Claim the account +# Overview +Source: https://docs.browser-use.com/cloud/agent/overview -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` +The agent is a hosted loop: it reads the page, decides an action, executes it, and repeats until the task is complete. You send a task, you get a result. -Response: +```python +from browser_use_sdk import BrowserUse -```json -{ - "claim_url": "https://..." -} +client = BrowserUse() +result = client.run("List the top 5 posts on Hacker News with their points") +print(result.output) ``` -The claim URL is valid for 1 hour. +Each run gets its own [stealth cloud browser](https://docs.browser-use.com/cloud/browser/stealth) with proxies and CAPTCHA handling already on. No browser management, no selectors, no waiting logic. + +## When to use the agent + +The agent fits tasks where you care about the outcome, not the exact clicks: data extraction from sites that change layout, workflows across several pages, form submission, or anything you'd rather describe than script. If you need pixel-exact control or deterministic repetition, drive a [browser session](https://docs.browser-use.com/cloud/browser/overview) directly instead, or record an agent run once and replay it with [cache scripts](https://docs.browser-use.com/cloud/agent/cache-script). + +## What the agent can do + +- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output) — get results as typed JSON matching your schema +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue in the same browser with context intact +- [Streaming](https://docs.browser-use.com/cloud/agent/streaming) — watch steps as they happen +- [Workspaces](https://docs.browser-use.com/cloud/agent/workspaces) — files the agent reads and writes during a run +- [Human-in-the-loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) — take over the browser mid-task, then hand back +- [Models](https://docs.browser-use.com/cloud/agent/models) — pick the LLM that drives the loop +- [Cache scripts](https://docs.browser-use.com/cloud/agent/cache-script) — record a run, replay it without LLM calls + +## Next + +Start with the [agent quickstart](https://docs.browser-use.com/cloud/agent/quickstart). For latency and cost tuning, see [Performance & speed](https://docs.browser-use.com/cloud/agent/performance). + # Introduction Source: https://docs.browser-use.com/cloud/agent/quickstart @@ -238,8 +325,8 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", + "List the top 20 posts on Hacker News today with their points", + model="claude-sonnet-4.6", ) print(result.output) ``` @@ -260,6 +347,44 @@ curl -X POST https://api.browser-use.com/api/v3/sessions \ -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6"}' ``` +## Bring your own key + +Connect your own Anthropic, OpenAI, or Google API key. You pay your provider directly + a 0.2× orchestration fee on provider list token prices. + +1. Add your provider key in the dashboard under **Settings → API Keys → Bring Your Own Key**. +2. Pass `use_own_key=True` on the session: + +```python Python +result = await client.run( + "List the top 20 posts on Hacker News today with their points", + model="claude-sonnet-4.6", + use_own_key=True, +) +``` +```typescript TypeScript +const result = await client.run( + "List the top 20 posts on Hacker News today with their points", + { model: "claude-sonnet-4.6", useOwnKey: true }, +); +``` +```bash curl +curl -X POST https://api.browser-use.com/api/v3/sessions \ + -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6", "useOwnKey": true}' +``` + +To default every session on a client to BYOK, set it once on the constructor: + +```python Python +client = AsyncBrowserUse(use_own_key=True) +``` +```typescript TypeScript +const client = new BrowserUse({ useOwnKey: true }); +``` + +The provider key on your project must match the model you pick — Claude models use your Anthropic key, GPT models use your OpenAI key, Gemini models use your Google key. + # Structured output Source: https://docs.browser-use.com/cloud/agent/structured-output @@ -274,20 +399,20 @@ from browser_use_sdk.v3 import AsyncBrowserUse from pydantic import BaseModel class Post(BaseModel): -name: str -points: int -comments: int + name: str + points: int + comments: int class HNPosts(BaseModel): -posts: list[Post] + posts: list[Post] client = AsyncBrowserUse() result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, + "List the top 20 posts on Hacker News today with their points", + output_schema=HNPosts, ) for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") + print(f"{post.name} ({post.points} pts, {post.comments} comments)") ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -329,12 +454,12 @@ client = AsyncBrowserUse() session = await client.sessions.create() result1 = await client.run( -"Go to amazon.com, search for laptops, and open the first result", -session_id=session.id, + "Go to amazon.com, search for laptops, and open the first result", + session_id=session.id, ) result2 = await client.run( -"Extract the customer reviews", -session_id=session.id, + "Extract the customer reviews", + session_id=session.id, ) await client.sessions.stop(session.id) @@ -377,7 +502,7 @@ client = AsyncBrowserUse() run = client.run("Find the top story on Hacker News") async for msg in run: -print(f"[{msg.role}] {msg.summary}") + print(f"[{msg.role}] {msg.summary}") print(run.result.output) ``` @@ -410,17 +535,17 @@ Use `stop(strategy="task")` to cancel the current task without destroying the se ```python Python run = client.run("Find the top story on Hacker News") async for msg in run: -if should_cancel(): - await client.sessions.stop(run.session_id, strategy="task") - break + if should_cancel(): + await client.sessions.stop(run.session_id, strategy="task") + break # Session is now idle — send a different task or close it ``` ```typescript TypeScript const run = client.run("Find the top story on Hacker News"); for await (const msg of run) { if (shouldCancel()) { -await client.sessions.stop(run.sessionId!, { strategy: "task" }); -break; + await client.sessions.stop(run.sessionId!, { strategy: "task" }); + break; } } // Session is now idle — send a different task or close it @@ -441,15 +566,15 @@ session = await client.sessions.create(task="Find the top story on Hacker News") cursor = None while True: -msgs = await client.sessions.messages(session.id, after=cursor, limit=100) -for m in msgs.messages: - print(f"[{m.role}] {m.summary}") - cursor = m.id + msgs = await client.sessions.messages(session.id, after=cursor, limit=100) + for m in msgs.messages: + print(f"[{m.role}] {m.summary}") + cursor = m.id -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) + s = await client.sessions.get(session.id) + if s.status.value in ("idle", "stopped", "error", "timed_out"): + break + await asyncio.sleep(2) print(s.output) ``` @@ -465,14 +590,14 @@ let cursor: string | undefined; while (true) { const msgs = await client.sessions.messages(session.id, { after: cursor, limit: 100 }); for (const m of msgs.messages) { -console.log(`[${m.role}] ${m.summary}`); -cursor = m.id; + console.log(`[${m.role}] ${m.summary}`); + cursor = m.id; } const s = await client.sessions.get(session.id); if (["idle", "stopped", "error", "timed_out"].includes(s.status)) { -console.log(s.output); -break; + console.log(s.output); + break; } await new Promise((r) => setTimeout(r, 2000)); } @@ -506,8 +631,8 @@ await client.workspaces.upload(workspace.id, "people.csv") # Agent can now read it result = await client.run( -"Read people.csv and tell me who works at Google", -workspace_id=workspace.id, + "Read people.csv and tell me who works at Google", + workspace_id=workspace.id, ) print(result.output) ``` @@ -547,8 +672,8 @@ workspace = await client.workspaces.create(name="my-workspace") # Agent creates a file result = await client.run( -"Go to Hacker News and save the top 3 posts as posts.json", -workspace_id=workspace.id, + "Go to Hacker News and save the top 3 posts as posts.json", + workspace_id=workspace.id, ) # Download a single file @@ -557,7 +682,7 @@ await client.workspaces.download(workspace.id, "posts.json", to="./posts.json") # Or download everything paths = await client.workspaces.download_all(workspace.id, to="./output") for p in paths: -print(f"Downloaded: {p}") + print(f"Downloaded: {p}") ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -588,7 +713,7 @@ workspace = await client.workspaces.get(workspace_id) updated = await client.workspaces.update(workspace_id, name="renamed") response = await client.workspaces.list() for w in response.items: -print(w.id, w.name) + print(w.id, w.name) await client.workspaces.delete(workspace_id) ``` ```typescript TypeScript @@ -612,7 +737,7 @@ await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") # List files in a subdirectory files = await client.workspaces.files(workspace.id, prefix="reports/") for f in files.files: -print(f.path, f.size) + print(f.path, f.size) # Download only files from a subdirectory await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") @@ -637,7 +762,7 @@ await client.workspaces.downloadAll(workspace.id, { to: "./output", prefix: "rep # List all files files = await client.workspaces.files(workspace.id) for f in files.files: -print(f.path, f.size) + print(f.path, f.size) # Delete a single file await client.workspaces.delete_file(workspace.id, path="old-report.pdf") @@ -686,14 +811,14 @@ workspace = await client.workspaces.create(name="my-scraper") # First call — agent explores, creates script (~$0.10, ~60s) result = await client.run( -"Get the top @{{5}} stories from https://news.ycombinator.com as JSON", -workspace_id=str(workspace.id), + "Get the top @{{5}} stories from https://news.ycombinator.com as JSON", + workspace_id=str(workspace.id), ) # Second call — cached script, different param ($0 LLM, ~5s) result2 = await client.run( -"Get the top @{{10}} stories from https://news.ycombinator.com as JSON", -workspace_id=str(workspace.id), + "Get the top @{{10}} stories from https://news.ycombinator.com as JSON", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -755,17 +880,17 @@ Run once, then loop over different keywords at $0 LLM each: ```python Python # Agent figures out how to scrape intro.co on first call result = await client.run( -"Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", -workspace_id=str(workspace.id), + "Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", + workspace_id=str(workspace.id), ) # Instant reruns with different keywords for keyword in ["CEO", "marketing", "finance", "e-commerce"]: -result = await client.run( - f"Go to @{{{{https://intro.co/marketplace}}}} and get all @{{{{{keyword}}}}} experts as JSON", - workspace_id=str(workspace.id), -) -print(f"{keyword}: {result.output}, LLM cost: ${result.llm_cost_usd}") + result = await client.run( + f"Go to @{{{{https://intro.co/marketplace}}}} and get all @{{{{{keyword}}}}} experts as JSON", + workspace_id=str(workspace.id), + ) + print(f"{keyword}: {result.output}, LLM cost: ${result.llm_cost_usd}") ``` ```typescript TypeScript // Agent figures out how to scrape intro.co on first call @@ -777,8 +902,8 @@ let result = await client.run( // Instant reruns with different keywords for (const keyword of ["CEO", "marketing", "finance", "e-commerce"]) { result = await client.run( -`Go to @{{https://intro.co/marketplace}} and get all @{{${keyword}}} experts as JSON`, -{ workspaceId: workspace.id }, + `Go to @{{https://intro.co/marketplace}} and get all @{{${keyword}}} experts as JSON`, + { workspaceId: workspace.id }, ); console.log(`${keyword}: ${result.output}`); } @@ -790,14 +915,14 @@ Append empty brackets `@{{}}` to signal "cache this exact task": ```python Python result = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), + "Get the current Bitcoin price from coinmarketcap.com @{{}}", + workspace_id=str(workspace.id), ) # Same task again — cached result2 = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), + "Get the current Bitcoin price from coinmarketcap.com @{{}}", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -817,14 +942,14 @@ result = await client.run( ```python Python result = await client.run( -"Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", -workspace_id=str(workspace.id), + "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", + workspace_id=str(workspace.id), ) # Different countries — cached result2 = await client.run( -"Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", -workspace_id=str(workspace.id), + "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -845,16 +970,16 @@ result = await client.run( ```python Python # Force-enable without brackets result = await client.run( -"Get the top stories from Hacker News", -workspace_id=str(workspace.id), -cache_script=True, + "Get the top stories from Hacker News", + workspace_id=str(workspace.id), + cache_script=True, ) # Force-disable even with brackets result = await client.run( -"Explain what @{{templates}} means in Jinja", -workspace_id=str(workspace.id), -cache_script=False, + "Explain what @{{templates}} means in Jinja", + workspace_id=str(workspace.id), + cache_script=False, ) ``` ```typescript TypeScript @@ -878,7 +1003,7 @@ You can download and inspect the scripts the agent created: ```python Python files = await client.workspaces.files(workspace.id, prefix="scripts/") for f in files.files: -print(f"{f.path} ({f.size} bytes)") + print(f"{f.path} ({f.size} bytes)") # Download a script to inspect it await client.workspaces.download(workspace.id, "scripts/a7f3b2c1.py", to="./my_script.py") @@ -953,8 +1078,8 @@ print(f"Live view: {session.live_url}") # 2. Agent does the first part result = await client.run( -"Go to amazon.com and search for noise cancelling headphones", -session_id=session.id, + "Go to amazon.com and search for noise cancelling headphones", + session_id=session.id, ) print(result.output) @@ -963,8 +1088,8 @@ input("Press Enter after you've selected a product in the live view...") # 4. Agent continues where the human left off result = await client.run( -"Get the details of the selected product — name, price, and rating", -session_id=session.id, + "Get the details of the selected product — name, price, and rating", + session_id=session.id, ) print(result.output) @@ -1008,133 +1133,519 @@ await client.sessions.stop(session.id); -# Introduction Stealth -Source: https://docs.browser-use.com/cloud/browser/stealth +# Performance & speed +Source: https://docs.browser-use.com/cloud/agent/performance -See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). +Agent latency has four sources, in descending order of impact: LLM inference per step, number of steps, page-load waits, and input mechanics. Tune them in that order. -## What's included +## Model choice -Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. +The model runs on every step, so it dominates end-to-end time. Smaller models cut per-step latency at some cost in reliability on hard pages. See [Models](https://docs.browser-use.com/cloud/agent/models) for the current lineup and speed characteristics. -- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors. -- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster. -- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services. +## Fewer, cheaper steps -## Residential proxies +- **`use_thinking`** — disables the model's extended reasoning per step. +- **`use_judge`** — the post-task quality evaluator; disabling it saves a final LLM call when you don't need verification. +- **`use_vision`** — controls whether screenshots are sent to the model; text-only steps are faster and cheaper, at the cost of visual grounding. +- **`max_history_items`** — caps how much history is resent each step; smaller history means smaller prompts. -Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. +{/* TEAM REVIEW: confirm which of these parameters are exposed on cloud v3/v4 runs vs library-only, and document defaults per surface. Evals found them documented only in legacy v2 docs — flash_mode was removed as inaccurate, verify the rest are current before publish. */} +## Waits and page loads -# Proxies -Source: https://docs.browser-use.com/cloud/browser/proxies +The agent waits between actions and for pages to settle. In the library these are browser-level settings (`wait_between_actions`, `minimum_wait_page_load_time`, `wait_for_network_idle_page_load_time`); lowering them speeds up action-dense tasks on fast sites and risks acting before slow pages are ready. +{/* TEAM REVIEW: reconcile the documented default for wait_between_actions (docs said 0.5s, source says 0.1s) and state the correct values here. */} -A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. +## Typing speed -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +Text is typed character by character through CDP with a small fixed delay per keystroke. This is deliberate: instant-fill is a bot tell, and human-paced input is part of staying unblocked. If a form fill feels slow, that's the trade-off working. -client = AsyncBrowserUse() -browser = await client.browsers.create(proxy_country_code="de") -print(browser.cdp_url) # ws://... -print(browser.live_url) # debug view +{/* TEAM REVIEW: the per-character delay is hardcoded at 5ms in the library source and documented nowhere. Confirm the value, whether cloud uses the same, and whether we want to expose it as a config field (product ticket exists). */} -# With an agent: -# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +## The fast path, all together + +```python +result = await client.run( + "Get the price of iPhone 16 on amazon.de", + use_thinking=False, + use_judge=False, + use_vision=False, +) ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); -const browser = await client.browsers.create({ proxyCountryCode: "de" }); -console.log(browser.cdpUrl); -console.log(browser.liveUrl); +{/* TEAM REVIEW: verify this exact snippet against the current v3/v4 run signature before publish. */} -// With an agent: -// const result = await client.run("Get the price of iPhone 16 on amazon.de", { proxyCountryCode: "de" }); -``` +## Deterministic replay: skip the LLM entirely -## Disable proxies +For workflows you run repeatedly, do the slow run once and replay it: [cache scripts](https://docs.browser-use.com/cloud/agent/cache-script) re-execute a recorded run without LLM calls, which is faster and cheaper than any tuning above. -If your use case does not need proxies, for example QA testing. +## Further reading -```python Python -browser = await client.browsers.create(proxy_country_code=None) +- [Speed matters: how Browser Use achieves the fastest agent execution](https://browser-use.com/posts/speed-matters) +- [The fastest web agent in the world](https://browser-use.com/posts/llm-gateway) +- [What LLM model should I use for Browser Use?](https://browser-use.com/posts/what-model-to-use) — the speed/accuracy tradeoff per model -# With an agent: -# result = await client.run("Go to http://localhost:3000", proxy_country_code=None) -``` -```typescript TypeScript -const browser = await client.browsers.create({ proxyCountryCode: null }); -// With an agent: -// const result = await client.run("Go to http://localhost:3000", { proxyCountryCode: null }); -``` +# Overview +Source: https://docs.browser-use.com/cloud/browser/overview -## Custom proxy -Bring your own proxy server (HTTP or SOCKS5). +A Browser Use cloud browser is a real Chromium instance running on our infrastructure that your code controls remotely over the Chrome DevTools Protocol (CDP). Create one with an API call, get back a `cdpUrl`, and drive it with Playwright, Puppeteer, or any CDP client, the same way you'd drive a local browser. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +The difference from local Chromium is what's built in. Every session runs our [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with anti-fingerprinting patches, [automatic CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies) in your choice of 195+ countries. None of it needs configuration. -client = AsyncBrowserUse() -browser = await client.browsers.create( -custom_proxy={ - "host": "proxy.example.com", - "port": 8080, - "username": "user", - "password": "pass", -}, -) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +## When to use a cloud browser -const client = new BrowserUse(); -const browser = await client.browsers.create({ - customProxy: { -host: "proxy.example.com", -port: 8080, -username: "user", -password: "pass", - }, -}); -``` +- **Your Playwright/Puppeteer scripts get blocked.** Same code, but running on infrastructure that sites treat as a normal user. +- **You don't want to run browsers.** No Chrome processes, no headless servers, no scaling browser pools. +- **You're building your own agent.** Full CDP access means any framework or custom tooling works. You can also run the [open-source Browser Use agent on a cloud browser](https://docs.browser-use.com/cloud/browser/open-source-agent). +- **You need a watchable, recordable session.** Every session has a [live view](https://docs.browser-use.com/cloud/browser/live-preview) you can open or embed, and optional recording. +If you'd rather describe the task and let AI do the driving, use the [Agent](https://docs.browser-use.com/cloud/agent/overview) instead. The two combine: agents run inside browser sessions, and you can connect your own code to the browser behind an agent run. -# Live preview & recording -Source: https://docs.browser-use.com/cloud/browser/live-preview +## How it fits together +1. [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — SDK, REST, or a single WebSocket URL +2. Connect your framework — [Playwright](https://docs.browser-use.com/cloud/browser/playwright), [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer), or [Selenium](https://docs.browser-use.com/cloud/browser/selenium) +3. Automate as usual — the session behaves like local Chromium with better manners from websites +4. [Manage the session](https://docs.browser-use.com/cloud/browser/sessions) — timeouts, stopping, what you're billed for - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +## Logging into websites + +Sessions start clean by default. To carry login state across sessions, use [profiles / cookie sync](https://docs.browser-use.com/cloud/guides/profile-sync), [authentication](https://docs.browser-use.com/cloud/guides/authentication), and [2FA support](https://docs.browser-use.com/cloud/guides/2fa). + +## Further reading + +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) — how the cloud browser is built +- [Closer to the Metal: Leaving Playwright for CDP](https://browser-use.com/posts/playwright-to-cdp) — why the browser is driven over CDP +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and the [benchmark results](https://browser-use.com/benchmarks) (84.8% BrowserBench, 81% bypass on high-security sites) + + +# Create a browser session +Source: https://docs.browser-use.com/cloud/browser/create -`liveUrl` is returned on session creation. + +Three ways to create a session. All of them return a browser with stealth, CAPTCHA solving, and a residential proxy already on. + +## SDK ```python Python from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +browser = await client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) # connect any CDP client here +print(browser.live_url) # watch the session in a browser tab ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Check how many GitHub stars browser-use has", -}); -console.log(session.liveUrl); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); +console.log(browser.liveUrl); ``` -`liveUrl` is also returned when creating a standalone browser session: +## REST -```python Python -browser = await client.browsers.create() -print(browser.live_url) +```bash +curl -X POST "https://api.browser-use.com/api/v3/browsers" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode": "us", "timeout": 60}' +``` + +## WebSocket URL (no SDK, no create call) + +Connect directly and the session is created for you. Configuration goes in query parameters, and the session stops when the socket disconnects. + +```text +wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us +``` + +## Parameters + +All parameters are optional. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `profileId` | `string` (UUID) | — | Load a saved [profile](https://docs.browser-use.com/cloud/guides/profile-sync) (cookies, localStorage) into the session. | +| `proxyCountryCode` | `string` | `us` | Residential proxy country. Set to `null` to disable the proxy. | +| `timeout` | `int` | `60` | Session lifetime in minutes, 1–240. The session stops automatically when it expires. | +| `browserScreenWidth` | `int` | — | Screen width in pixels, 320–6144. | +| `browserScreenHeight` | `int` | — | Screen height in pixels, 320–3456. | +| `allowResizing` | `bool` | `false` | Allow window resizing during the session. Not recommended: resizing reduces stealth. | +| `customProxy` | `object` | — | Bring your own proxy instead of ours. | +| `enableRecording` | `bool` | `false` | Record the session. The video is available as `recordingUrl` after the session stops. | + +{/* TEAM REVIEW: the WSS connection path previously documented timeout default as 15 minutes; the v3 API spec says 60. Confirm which is correct per method and align the framework pages. */} + +## Response + +`201` with a browser session object: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-15T21:00:00Z", + "startedAt": "2026-07-15T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in REST and TypeScript (`cdpUrl`, `liveUrl`), snake_case in Python (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable, check them before connecting. + +## Errors + +| Status | Meaning | +|--------|---------| +| `403` | Session timeout limit exceeded for your plan. | +| `404` | The `profileId` doesn't exist. | +| `422` | Invalid parameter value. | +| `429` | Too many concurrent active sessions. Stop unused sessions or raise your limit. | + +## Next + +- Connect with [Playwright](https://docs.browser-use.com/cloud/browser/playwright), [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer), or [Selenium](https://docs.browser-use.com/cloud/browser/selenium) +- [Manage the session](https://docs.browser-use.com/cloud/browser/sessions): lifecycle, stopping, billing + + +# Manage browser sessions +Source: https://docs.browser-use.com/cloud/browser/sessions + + +A session has two states: `active` and `stopped`. It leaves `active` in exactly three ways: you stop it, its timeout expires, or (WebSocket connections only) the socket disconnects. + +## Stopping a session + +Stopping is an update, not a delete, and it cannot be undone. + +```python Python +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +await client.browsers.stop(browser.id); +``` +```bash REST +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +There is no `POST /browsers/{id}/stop` endpoint. If you're getting a `404` on a stop call, this is why. + +Stop sessions as soon as you're done with them. Browser time is billed at $0.02/hour until the session stops or times out, whichever comes first. + +```python +browser = await client.browsers.create() +try: + ... # your automation +finally: + await client.browsers.stop(browser.id) +``` + +## Disconnecting vs stopping + +The two connection styles behave differently when your client goes away: + +| | Client disconnects | Session keeps running? | +|---|---|---| +| WebSocket URL (`wss://connect.browser-use.com`) | Socket closes | No — the session stops automatically | +| SDK / REST (`browsers.create()` + CDP) | `pw_browser.close()` only detaches your client | Yes — until you call stop or the timeout expires | + +The SDK behavior is what lets you disconnect and reconnect to the same session, but it also means forgotten sessions keep billing. If you see `429 Too many concurrent active sessions`, list and stop the strays: + +```python +sessions = await client.browsers.list(filter_by="active") +for s in sessions.items: + await client.browsers.stop(s.id) +``` + +## Timeouts + +Every session has a lifetime set at creation: `timeout` in minutes, default `60`, maximum `240` (4 hours). The expiry moment comes back as `timeoutAt` in the session object. A timed-out session stops automatically and cannot be extended or reused, so if a workflow might outlive the default, set the timeout up front: + +```python +browser = await client.browsers.create(timeout=240) +``` + +## Inspecting sessions + +```python +browser = await client.browsers.get(session_id) # one session +sessions = await client.browsers.list(page_size=20) # paginated, filter_by="active" | "stopped" +``` + +The session object carries the operational fields: `status`, `timeoutAt`, `startedAt`, `finishedAt`, live and CDP URLs, plus cost tracking (`browserCost`, `proxyCost`, `proxyUsedMb`). + +## Recordings and downloads + +- Create the session with `enableRecording: true` and `recordingUrl` is populated after the session stops. It is `null` while the session runs and shortly after stopping while the video is processed. +- Files downloaded by the browser during the session are listed at `GET /browsers/{session_id}/downloads`. + +## Related + +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all creation parameters +- [Live preview](https://docs.browser-use.com/cloud/browser/live-preview) — watch or embed a running session + + +# Introduction Stealth +Source: https://docs.browser-use.com/cloud/browser/stealth + + +See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). + +## What's included + +Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. + +- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors. +- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster. +- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services. + +## Residential proxies + +Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) + + +# CAPTCHA Solving +Source: https://docs.browser-use.com/cloud/browser/captcha + + +Browser Use remote browsers are state-of-the-art for stealth. By using a custom Chromium fork with dozens of patches, web agents get blocked by CAPTCHAs and anti-bot systems noticeably less on the websites users care about most. Read how we do it in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and see the [benchmark results](https://browser-use.com/benchmarks): 84.8% on BrowserBench and 81% bypass on high-security sites, ahead of every other provider. + +When a CAPTCHA does appear, remote browsers have **automatic CAPTCHA solving** built in. You do not need to configure anything, on the browser or on the attached agent or automation library (Playwright, Puppeteer, Selenium). It is on by default on every plan, including the [free tier](https://docs.browser-use.com/cloud/pricing). + +## Setting up a stealth browser + +There is nothing to turn on. Stealth and CAPTCHA solving come with every session. Start one: + +SDK, REST, or a single WebSocket URL. +Playwright, Puppeteer, or Selenium over CDP. +What the hardened Chromium fork does. +Residential IPs in 195+ countries, on by default. + +## FAQ + +**Does the open-source library solve CAPTCHAs?** + +Without remote browsers, [open-source](https://github.com/browser-use/browser-use) agents have no stealth or CAPTCHA solving. Giving your agent stealth is easy: run it on a remote browser with a single parameter. See [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent). + +**Can I use a third-party CAPTCHA solver?** + +No, we do not support third-party CAPTCHA solver plugins on the browser. If your CAPTCHAs are not being solved properly, reach out and we will look into it. + +**Do I need to enable anything for CAPTCHA solving?** + +No. Remote browsers solve CAPTCHAs for you automatically. + +## Further reading + +- [Prove you are a robot: CAPTCHAs for agents](https://browser-use.com/posts/prove-you-are-a-robot) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) + + +# Proxies +Source: https://docs.browser-use.com/cloud/browser/proxies + + +A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create(proxy_country_code="de") +print(browser.cdp_url) # ws://... +print(browser.live_url) # debug view + +# With an agent: +# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "de" }); +console.log(browser.cdpUrl); +console.log(browser.liveUrl); + +// With an agent: +// const result = await client.run("Get the price of iPhone 16 on amazon.de", { proxyCountryCode: "de" }); +``` + +## Disable proxies + +If your use case does not need proxies, for example QA testing. + +```python Python +browser = await client.browsers.create(proxy_country_code=None) + +# With an agent: +# result = await client.run("Go to http://localhost:3000", proxy_country_code=None) +``` +```typescript TypeScript +const browser = await client.browsers.create({ proxyCountryCode: null }); + +// With an agent: +// const result = await client.run("Go to http://localhost:3000", { proxyCountryCode: null }); +``` + +## Custom proxy + +Bring your own proxy server (HTTP or SOCKS5). + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create( + custom_proxy={ + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + }, +) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create({ + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + }, +}); +``` + +## Further reading + +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) — how proxy quality affects bypass rates + + +# Screenshots +Source: https://docs.browser-use.com/cloud/browser/screenshots + + +A cloud browser session is a normal CDP endpoint, so screenshots work the way your framework takes them, and they save wherever your code runs. + +## Where screenshots are saved + +The most-asked question first: screenshots taken through Playwright or Puppeteer are written by *your* code, to a path *you* choose. Nothing is stored on the session unless you enable [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads). + +```python Python +from playwright.async_api import async_playwright +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create() + +async with async_playwright() as p: + pw = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw.contexts[0].pages[0] + await page.goto("https://example.com") + await page.screenshot(path="shots/example.png") # your machine, your path + +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +import { chromium } from "playwright"; +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create(); + +const pw = await chromium.connectOverCDP(browser.cdpUrl); +const page = pw.contexts()[0].pages()[0]; +await page.goto("https://example.com"); +await page.screenshot({ path: "shots/example.png" }); + +await client.browsers.stop(browser.id); +``` + +## Full page, not just the viewport + +By default a screenshot captures the visible viewport. For the whole page, top to bottom: + +```python +await page.screenshot(path="full.png", full_page=True) +``` + +Playwright stitches the scroll automatically. The result contains page content only, no URL bar or browser chrome, because CDP screenshots capture the rendered page, not the window. + +## Resolution + +Screenshot dimensions follow the browser's screen size, set at [session creation](https://docs.browser-use.com/cloud/browser/create) with `browserScreenWidth` and `browserScreenHeight` (320–6144 × 320–3456). Set them explicitly if screenshots must match a target resolution: + +```python +browser = await client.browsers.create(browser_screen_width=1920, browser_screen_height=1080) +``` + +{/* TEAM REVIEW: document the default screen size when width/height are omitted, and whether recording resolution (1920x1080 reported by users) can differ from screenshot resolution — a user reported 1512x770 screenshots vs 1920x1080 recordings. */} + +## Screenshots vs recording + +Screenshots are moments; [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads) is the whole session as video (`enableRecording: true` at create, `recordingUrl` after stop). For debugging agent behavior, recording is usually what you want; for artifacts and QA evidence, screenshots. + +## From agent tasks + +Ask the agent to take screenshots as part of a task and collect them from the run's [workspace files](https://docs.browser-use.com/cloud/agent/workspaces). + +{/* TEAM REVIEW: add the exact API for retrieving agent step screenshots (the v1 /screenshots endpoint users reference) and note whether those images carry element highlight overlays — users ask for unmarked versions. */} + + +# Live preview & recording +Source: https://docs.browser-use.com/cloud/browser/live-preview + + + Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). + +`liveUrl` is returned on session creation. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +session = await client.sessions.create(task="Check how many GitHub stars browser-use has") +print(session.live_url) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const session = await client.sessions.create({ + task: "Check how many GitHub stars browser-use has", +}); +console.log(session.liveUrl); +``` + +`liveUrl` is also returned when creating a standalone browser session: + +```python Python +browser = await client.browsers.create() +print(browser.live_url) ``` ```typescript TypeScript const browser = await client.browsers.create(); @@ -1196,14 +1707,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Check how many GitHub stars browser-use has", -enable_recording=True, + "Check how many GitHub stars browser-use has", + enable_recording=True, ) # Waits up to 15s for recording to be ready. Returns [] if no browser was opened. urls = await client.sessions.wait_for_recording(result.id) for url in urls: -print(url) # presigned MP4 download URL + print(url) # presigned MP4 download URL ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1244,29 +1755,32 @@ console.log(stopped.recordingUrl); // presigned MP4 download URL -# Playwright, Puppeteer, Selenium -Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium +# Playwright +Source: https://docs.browser-use.com/cloud/browser/playwright -Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. +Run your Playwright scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Playwright scripts and want to run them on stealth infrastructure +- You need pixel-perfect control (screenshots, specific click coordinates, form filling) +- You want to combine agent tasks with manual browser automation ## Option 1: WebSocket URL (no SDK) Connect with a single URL. All configuration is passed as query parameters. -### Playwright - ```python Python from playwright.async_api import async_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" async with async_playwright() as p: -browser = await p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await browser.close() + browser = await p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -1282,40 +1796,7 @@ await browser.close(); // Browser is automatically stopped when the WebSocket disconnects ``` -### Puppeteer - -```typescript -import puppeteer from "puppeteer-core"; - -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); -const [page] = await browser.pages(); -await page.goto("https://example.com"); -console.log(await page.title()); -await browser.close(); -``` - -### Selenium - -Selenium requires a local WebSocket proxy to connect to Browser Use's remote CDP endpoint. Use [selenium-wire](https://github.com/wkeeling/selenium-wire) or connect through Playwright's CDP bridge instead: - -```python -from playwright.sync_api import sync_playwright - -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: -browser = p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -page.goto("https://example.com") -print(page.title()) -browser.close() -``` - - Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use Playwright or Puppeteer instead. - -## Query parameters +### Query parameters | Parameter | Type | Description | |-----------|------|-------------| @@ -1328,9 +1809,7 @@ browser.close() ## Option 2: SDK -Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright +Create a browser via the SDK, get a `cdp_url`, and connect. The SDK also gives you a `live_url` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview). ```python Python from browser_use_sdk.v3 import AsyncBrowserUse @@ -1342,11 +1821,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com print(browser.live_url) # https://live.browser-use.com?wss=... async with async_playwright() as p: -pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) -page = pw_browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await pw_browser.close() + pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw_browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await pw_browser.close() await client.browsers.stop(browser.id) ``` @@ -1368,7 +1847,98 @@ await pwBrowser.close(); await client.browsers.stop(browser.id); ``` -### Puppeteer +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`) and snake_case in the Python SDK (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. + +### Stopping a session over REST + +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: + +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +## Gotchas + + Use `connect_over_cdp()` / `connectOverCDP()`, **not** `connect()`. Playwright's `connect()` expects a Playwright-protocol server and fails against a CDP endpoint with an opaque `Protocol error (Browser.getVersion)`. + +- **Reuse the existing context.** The session already has a context and page open — use `browser.contexts[0].pages[0]` instead of `browser.new_context()`, so you keep the stealth fingerprint and any loaded [profile](https://docs.browser-use.com/cloud/browser/playwright#query-parameters). +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `pw_browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + +## See also + +- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Puppeteer +Source: https://docs.browser-use.com/cloud/browser/puppeteer + + +Run your Puppeteer scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Puppeteer scripts and want to run them on stealth infrastructure +- You want low-level CDP control from Node.js without managing Chrome yourself +- You want to combine agent tasks with manual browser automation + +## Option 1: WebSocket URL (no SDK) + +Connect with a single URL. All configuration is passed as query parameters. + +```typescript +import puppeteer from "puppeteer-core"; + +const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; + +const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); +const [page] = await browser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +await browser.close(); +// Browser is automatically stopped when the WebSocket disconnects +``` + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Option 2: SDK + +Create a browser via the SDK, then resolve the WebSocket endpoint. Unlike Playwright, Puppeteer can't connect to an HTTP CDP URL directly — fetch `/json/version` to get the `webSocketDebuggerUrl` first. ```typescript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1381,17 +1951,186 @@ const browser = await client.browsers.create(); const resp = await fetch(`${browser.cdpUrl}/json/version`); const { webSocketDebuggerUrl } = await resp.json(); -const pwBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); -const [page] = await pwBrowser.pages(); +const pptrBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); +const [page] = await pptrBrowser.pages(); await page.goto("https://example.com"); console.log(await page.title()); -await pwBrowser.close(); +await pptrBrowser.close(); await client.browsers.stop(browser.id); ``` +The SDK also gives you a `liveUrl` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview). + +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. + +### Stopping a session over REST + +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: + +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +## Gotchas + +- **Use `puppeteer-core`.** It's the connect-only package — installing full `puppeteer` downloads a local Chromium you'll never use. +- **`browserWSEndpoint` must be a `ws://`/`wss://` URL.** Passing the SDK's HTTPS `cdpUrl` directly fails; resolve it via `/json/version` as shown above. +- **Viewport.** Puppeteer applies its own 800×600 default viewport after connecting. Pass `defaultViewport: null` to `puppeteer.connect()` to keep the browser's real window size. +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + +## See also + +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Selenium +Source: https://docs.browser-use.com/cloud/browser/selenium + + +Browser Use's cloud browsers speak Chrome DevTools Protocol (CDP) over a remote WebSocket. Selenium can't consume that natively: its `debugger_address` option only supports local `host:port` connections, not remote `wss://` URLs. + +You have two practical paths. + +## Recommended: bridge through a CDP client + +If you're migrating Selenium scripts, connect through Playwright's sync API — the page-automation model (navigate, locate, click, read) maps one-to-one, and you get the [hardened stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) with no configuration. + +```python +from playwright.sync_api import sync_playwright + +WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) + browser.close() +# Browser is automatically stopped when the WebSocket disconnects +``` + +Common Selenium → Playwright equivalents: + +| Selenium | Playwright (sync) | +|---|---| +| `driver.get(url)` | `page.goto(url)` | +| `driver.find_element(By.CSS_SELECTOR, s)` | `page.locator(s)` | +| `element.click()` | `page.locator(s).click()` | +| `element.send_keys(text)` | `page.locator(s).fill(text)` | +| `driver.title` | `page.title()` | +| `WebDriverWait(...).until(...)` | built-in auto-waiting | +| `driver.quit()` | `browser.close()` | + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Alternative: keep Selenium with a local proxy + +If you must keep the Selenium API, run a local WebSocket-to-TCP proxy so Chrome's remote debugging endpoint appears as a local `host:port`, e.g. via [selenium-wire](https://github.com/wkeeling/selenium-wire). This adds a moving part we don't manage — for new code, prefer the CDP bridge above. + + Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use [Playwright](https://docs.browser-use.com/cloud/browser/playwright) or [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) instead. + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. +## See also + +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Cloud browser + open source agent +Source: https://docs.browser-use.com/cloud/browser/open-source-agent + + +The [open-source library](/open-source/introduction) runs the agent on your machine. By default it also runs the *browser* on your machine, which means no stealth, no residential proxy, and no CAPTCHA solving. This page connects the two: keep your local agent code, point it at a cloud browser. + +## Connect by CDP URL + +Create a cloud browser, then pass its CDP URL to the library's `Browser`: + +```python +import asyncio +from browser_use import Agent, Browser, ChatOpenAI +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse() + cloud_browser = await client.browsers.create(proxy_country_code="us") + + try: + agent = Agent( + task="Find the current price of iPhone 16 on amazon.de", + llm=ChatOpenAI(model="gpt-4o"), + browser=Browser(cdp_url=cloud_browser.cdp_url), + ) + await agent.run() + finally: + await client.browsers.stop(cloud_browser.id) + +asyncio.run(main()) +``` + +The agent behaves exactly as it does locally. The browser it drives is a [stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) with [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies), and you can watch it work through the session's `live_url`. + +{/* TEAM REVIEW: confirm the `use_cloud=True` shorthand on Browser() — parameter name, minimum library version, and whether it should be the primary example instead of the cdp_url form. */} + +## What you get, what you keep + +| | Stays yours | Comes from Cloud | +|---|---|---| +| Agent loop, prompts, custom tools | ✓ | | +| LLM choice and API keys | ✓ | | +| Browser runtime | | ✓ stealth Chromium | +| Proxy / IP | | ✓ residential, 195+ countries | +| CAPTCHA handling | | ✓ automatic | +| Live view and recording | | ✓ per session | + +Billing: only the browser session ($0.02/hour plus proxy data). Your LLM tokens go to your own provider. + +## Related + +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all session parameters +- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud) — the full decision guide +- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions) — always stop sessions when done + # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication @@ -1439,7 +2178,7 @@ profile = await client.profiles.create(name="work-account") # List all response = await client.profiles.list() for p in response.items: -print(p.id, p.name) + print(p.id, p.name) # Search by name response = await client.profiles.list(query="user-id-1") @@ -1484,8 +2223,12 @@ await client.profiles.delete(profileId); Profile state is only saved when the session ends. Always call `sessions.stop()` when you are done — if a session is left open or times out, changes may not be persisted. Every code path that uses a profile must stop the session, including error handlers. +## Further reading + +- [How to authenticate AI web agents](https://browser-use.com/posts/web-agent-authentication) -# Sync local and cloud cookies + +# Profiles / Cookie sync Source: https://docs.browser-use.com/cloud/guides/profile-sync @@ -1588,190 +2331,949 @@ await client.sessions.stop(newSession.id); Cookies expire. Some sites stay logged in for months, others expire daily. If your sessions start hitting login pages again, re-authenticate and save the profile. - Always call `sessions.stop()` after you're done — profile state is only saved when the session ends cleanly. + Always call `sessions.stop()` after you're done — profile state is only saved when the session ends cleanly. + +--- + +## 2. Human in the loop + +Let the agent navigate to the login page, then a human takes over to complete 2FA via the live browser view. The agent continues after. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +session = await client.sessions.create() +print(f"Live view: {session.live_url}") + +# Agent navigates to login +result = await client.run( + "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", + session_id=session.id, +) + +# Human completes 2FA in the live view +input("Complete 2FA in the live view, then press Enter...") + +# Agent continues +result = await client.run( + "You are now logged in. Go to the dashboard and export the monthly report", + session_id=session.id, +) +print(result.output) +await client.sessions.stop(session.id) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; +import * as readline from "readline"; + +const client = new BrowserUse(); +const session = await client.sessions.create(); +console.log(`Live view: ${session.liveUrl}`); + +// Agent navigates to login +await client.run( + "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", + { sessionId: session.id }, +); + +// Human completes 2FA in the live view +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); +await new Promise((resolve) => rl.question("Complete 2FA in the live view, then press Enter...", resolve)); +rl.close(); + +// Agent continues +const result = await client.run( + "You are now logged in. Go to the dashboard and export the monthly report", + { sessionId: session.id }, +); +console.log(result.output); +await client.sessions.stop(session.id); +``` + +See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. + +--- + +## 3. Agent Mail + +When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. + +Agent Mail is **enabled by default** (`agentmail=True`). Each session gets a unique email address (`session.agentmail_email`). The agent can send and receive emails during the task. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() + +result = await client.run( + """ + 1. Go to example.com/signup + 2. Sign up with the agent's email address (use the email available to you) + 3. Check your email inbox for the verification code + 4. Enter the code on the website + 5. Complete the registration + """, + agentmail=True, # default, shown for clarity +) +print(result.output) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); + +const result = await client.run( + `1. Go to example.com/signup + 2. Sign up with the agent's email address (use the email available to you) + 3. Check your email inbox for the verification code + 4. Enter the code on the website + 5. Complete the registration`, + { agentmail: true }, // default, shown for clarity +); +console.log(result.output); +``` + +### For end-client automation + +If you're automating on behalf of your users and they need to receive 2FA codes: + +1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). +2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. + +This way, your client's real email stays private — the agent only sees the forwarded verification emails. + +### Connect external email via Composio + +You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. + +--- + +## 4. TOTP secret in prompt + +If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. + +When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() + +# The TOTP secret from your authenticator setup — NOT the 6-digit code +totp_secret = "JBSWY3DPEHPK3PXP" + +result = await client.run( + f""" + Log into example.com with username user@example.com and password mypassword. + When prompted for a 2FA code, generate one using pyotp: + + import pyotp + totp = pyotp.TOTP("{totp_secret}") + code = totp.now() + + Enter the generated code. + """, +) +print(result.output) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); + +// The TOTP secret from your authenticator setup — NOT the 6-digit code +const totpSecret = "JBSWY3DPEHPK3PXP"; + +const result = await client.run( + `Log into example.com with username user@example.com and password mypassword. + When prompted for a 2FA code, generate one using pyotp: + + import pyotp + totp = pyotp.TOTP("${totpSecret}") + code = totp.now() + + Enter the generated code.`, +); +console.log(result.output); +``` + +This works because the Browser Use agent can execute Python code as part of its task. The agent runs `pyotp.TOTP(secret).now()` to generate a time-based 6-digit code, then types it into the 2FA field. + +### Where to find TOTP secrets + +- **1Password**: Edit item → One-Time Password → Show secret +- **Google Authenticator**: During setup, click "Can't scan it?" to see the key +- **Authy**: Export via desktop app settings +- **Most sites**: Look for "manual entry" or "setup key" during 2FA enrollment + +--- + +## Which approach should I use? + +Start with **Profiles** — log in once, reuse cookies. If cookies expire frequently, add **TOTP secret in prompt** for fully automated re-login. +Use **Profiles** with one profile per user. For initial login, use **Human in the loop** — your user logs in once via the live view, then the agent reuses the session. For email 2FA, set up **Agent Mail** with email forwarding from your user. +Use **Agent Mail** (enabled by default). For end-client scenarios, have them forward 2FA emails to a dedicated inbox. +Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. + + +# MCP Server +Source: https://docs.browser-use.com/cloud/guides/mcp-server + + +``` +https://api.browser-use.com/v3/mcp +``` + +Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). + +## Claude Code + +```bash +claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp +``` + +## Claude Desktop + +Add to `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Cursor + +Add to `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Windsurf + +Add to `~/.codeium/windsurf/mcp_config.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "serverUrl": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Available Tools + +| Tool | Description | +|------|-------------| +| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | +| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | +| `send_task` | Send a follow-up task to an idle keep-alive session. | +| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | +| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | +| `list_sessions` | List recent sessions with status and cost. | +| `list_browser_profiles` | List browser profiles for authenticated tasks. | + + +# Webhooks +Source: https://docs.browser-use.com/cloud/guides/webhooks + + +Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). + +## Events + +| Event | When | +|-------|------| +| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | +| `test` | Webhook test ping | + +## Payload + +```json +{ + "type": "agent.task.status_update", + "timestamp": "2025-01-15T10:30:00Z", + "payload": { + "task_id": "task_abc123", + "session_id": "session_xyz", + "status": "idle", + "metadata": {} + } +} +``` + +## Signature verification + +Every webhook request includes two headers: + +- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload +- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent + +The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. + +```python Python +import hashlib +import hmac +import json +import time + +def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + return False + if abs(time.time() - ts) > 300: + return False + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() + return hmac.compare_digest(expected, signature) +``` +```typescript TypeScript +import { createHmac, timingSafeEqual } from "crypto"; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { + // Reject requests older than 5 minutes + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", secret).update(message).digest("hex"); + return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); +} +``` + +## Example: Express webhook handler + +```typescript +import express from "express"; +import { createHmac, timingSafeEqual } from "crypto"; + +const app = express(); +app.use(express.raw({ type: "application/json" })); + +const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +app.post("/webhook", (req, res) => { + const signature = req.headers["x-browser-use-signature"] as string; + const timestamp = req.headers["x-browser-use-timestamp"] as string; + + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { + return res.status(401).send("Request too old"); + } + + const body = req.body.toString(); + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); + + if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { + return res.status(401).send("Invalid signature"); + } + + if (payload.type === "agent.task.status_update") { + const { task_id, status, session_id } = payload.payload; + console.log(`Task ${task_id} is now ${status}`); + } + + res.status(200).send("OK"); +}); + +app.listen(3000); +``` + +## Example: FastAPI webhook handler + +```python +from fastapi import FastAPI, Request, HTTPException +import hashlib +import hmac +import json +import os +import time + +app = FastAPI() + +WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] + +@app.post("/webhook") +async def handle_webhook(request: Request): + body = await request.body() + signature = request.headers.get("x-browser-use-signature", "") + timestamp = request.headers.get("x-browser-use-timestamp", "") + + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + raise HTTPException(status_code=401, detail="Invalid timestamp") + if abs(time.time() - ts) > 300: + raise HTTPException(status_code=401, detail="Request too old") + + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() + + if not hmac.compare_digest(expected, signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + if payload["type"] == "agent.task.status_update": + task_id = payload["payload"]["task_id"] + status = payload["payload"]["status"] + print(f"Task {task_id} is now {status}") + + return {"status": "ok"} +``` + + For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. + + +# x402 (pay-per-request) +Source: https://docs.browser-use.com/cloud/guides/x402 + + +{/* prettier-ignore-start */} + +[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. + +x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. + + +**New to crypto?** Here's the gist: + +- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. +- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. +- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. +- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. + + +**Three ways to start, ranked by laziness:** + +One command. Claude does the wallet setup, funding walkthrough, and +verification for you. +One line in your Python or TypeScript app. Bring your own wallet. +Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. + +## Claude Code quickstart + +The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: + +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` + +Then in Claude Code: + +``` +> /x402 +``` + +Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. + + Already have a Browser Use Cloud account? The skill detects this and switches + to **top-up mode**, adding credits to that existing account instead of + creating a new, wallet-keyed one. + +## SDK quickstart + +The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. + +```bash Python +pip install "browser-use-sdk[x402]" +``` +```bash TypeScript +npm install browser-use-sdk @x402/fetch @x402/evm viem +``` + +```python Python +import asyncio +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base + result = await client.run("Go to example.com and tell me the heading.") + print(result.output) + +asyncio.run(main()) +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base +const result = await client.run("Go to example.com and tell me the heading."); +console.log(result.output); +``` + +Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: + +```python Python +client = AsyncBrowserUse() # auto-detects from env +``` +```typescript TypeScript +const client = new BrowserUse(); // auto-detects from env +``` + + Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. + +## Raw HTTP quickstart + +Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): + +```python +import asyncio + +from x402 import x402Client +from x402.http.clients import x402HttpxClient +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account + +async def main(): + client = x402Client() + register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) + + async with x402HttpxClient(client, timeout=120.0) as http: + response = await http.post( + "https://x402.api.browser-use.com/api/v3/sessions", + json={"task": "..."}, + ) + print(response.status_code, response.text[:500]) + +asyncio.run(main()) +``` + +`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. + +## What you need + +- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app +- **USD Coin (USDC) on Base mainnet** +- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) + +You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. + + +## Pricing and credits + +Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** + + **Mid-task drain still terminates the task.** Browser Use sessions run on a + worker that doesn't see x402, so once a long-running task starts and burns + through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause + and wait for the next x402 payment. The `$5` default exists so most tasks + complete without hitting this; for expensive models (e.g. Opus) or long + sessions, pre-fund with multiple requests before kicking off the task. + +See the [pricing page](https://browser-use.com/pricing) for model and browser costs. + +## Topping up an existing account + +If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: + +```python Python +import asyncio + +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse( + api_key="bu_...", # existing API key getting topped up + x402_private_key="0x...", # wallet that pays + base_url="https://x402.api.browser-use.com/api/v3", +) +async def main(): + result = await client.run("...") # $5 USDC charged, credited to the API key's project + print(result.output) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ + apiKey: "bu_...", + x402PrivateKey: "0x...", + baseUrl: "https://x402.api.browser-use.com/api/v3", +}); +const result = await client.run("..."); +``` + +When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: + +- Agents that ran out of free-tier credits and need to keep going +- Adding credits via crypto when you already have a regular Browser Use account +- Multi-wallet setups funding one shared account + +## Checking your credit balance + +When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. + +This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. + +To check how much credit that account has left, use the method below: + +```python Python +import asyncio + +from browser_use_sdk.v3 import get_wallet_balance + +async def main(): + balance = await get_wallet_balance("0x...") # same wallet private key you pay with + print(balance["total_credits_usd"]) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { getWalletBalance } from "browser-use-sdk/v3"; + +const balance = await getWalletBalance("0x..."); // same wallet private key you pay with +console.log(balance.total_credits_usd); +``` + +The response contains: + +| Field | Description | +| ------------------------ | ------------------------------------------------------------------------------- | +| `wallet` | The wallet address (lowercased) | +| `project_id` | The account (project) tied to your wallet that the credits live in | +| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | +| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | + + This is for accounts created from a wallet (the default x402 mode). If you're + [topping up an existing account](#topping-up-an-existing-account), check that + account's balance the normal way with your API key via + `client.billing.account()`. A wallet that has never paid yet has no account, + so the call returns `404` until the first payment. + + The SDK signs a fixed, server-defined message + ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with + Ethereum" mechanism) with your wallet's private key. The signature proves you + control the address without moving any funds. The server recovers the signer, + matches it to the wallet's project, and returns the balance. + +## How it works + +Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. + +A bit more detail: + +1. Your code makes a request (e.g. "run this task"). +2. The SDK auto-signs the payment from your wallet and resends the request. +3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. +4. We run your task and send back the result. + +## Wallet setup + +If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. + +Get the [MetaMask browser extension](https://metamask.io) via the official +site only. Create a new wallet, save the seed phrase somewhere offline, set +a password. +By default, most wallets only show Ethereum. You need to add **Base** (the +network we accept payments on) so your wallet can hold USDC there. +Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and +pay with credit card, bank, etc. The USDC lands directly in your wallet. +In MetaMask: click the account menu → **Account details** → **Private keys** +→ enter your password → copy. That string (starts with `0x`) is your +`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in +their account settings. + + Wallets hold real money, and anyone with the private key can drain it. Be + careful with your keys. + +## Advanced: bring your own x402 client + +For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: + +```python Python +from x402 import x402Client +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account +from browser_use_sdk.v3 import AsyncBrowserUse + +x402 = x402Client() +register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) +client = AsyncBrowserUse(x402=x402) + +``` + +```typescript TypeScript +import { x402Client } from "@x402/fetch"; +import { ExactEvmScheme } from "@x402/evm"; +import { privateKeyToAccount } from "viem/accounts"; +import { BrowserUse } from "browser-use-sdk/v3"; + +const x402 = new x402Client(); +x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); +const client = new BrowserUse({ x402 }); +``` + +## Troubleshooting + +Two likely causes: + +- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. +- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). + + + You haven't installed the optional x402 deps. Run `pip install + "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` + (TypeScript). + + We verified your payment request but couldn't credit your project, so we + deliberately did not settle on-chain. No USDC was moved, so just retry. This + is rare. + + Wait a few seconds. Settlement and credit grant happen in the same request, + but the response may be sent before the credit grant fully commits. If credits + still show `$0` after a few minutes, contact support with your wallet address. + (Conversely, if a payment settles but the request itself then fails, we + automatically reclaim the credits so you aren't charged for nothing.) + +`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. + +## Related + +- [x402 protocol spec](https://www.x402.org) +- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use +- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) + +{/* prettier-ignore-end */} + ---- +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code -## 2. Human in the loop -Let the agent navigate to the login page, then a human takes over to complete 2FA via the live browser view. The agent continues after. +[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is Anthropic's agentic coding tool that runs in the terminal. Add Browser Use and it gets full cloud browser automation — anti-detect profiles, CAPTCHA solving, residential proxies in 195+ countries, persistent profiles, and stealth browsing. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Setup -client = AsyncBrowserUse() -session = await client.sessions.create() -print(f"Live view: {session.live_url}") +**1. Install the CLI** -# Agent navigates to login -result = await client.run( -"Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", -session_id=session.id, -) +```bash +uv tool install browser-use +``` -# Human completes 2FA in the live view -input("Complete 2FA in the live view, then press Enter...") +**2. Verify the installation** -# Agent continues -result = await client.run( -"You are now logged in. Go to the dashboard and export the monthly report", -session_id=session.id, -) -print(result.output) -await client.sessions.stop(session.id) +```bash +browser-use doctor ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; -const client = new BrowserUse(); -const session = await client.sessions.create(); -console.log(`Live view: ${session.liveUrl}`); +**3. Register the skill** -// Agent navigates to login -await client.run( - "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", - { sessionId: session.id }, -); +Register the Browser Use skill with the installed CLI: -// Human completes 2FA in the live view -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => rl.question("Complete 2FA in the live view, then press Enter...", resolve)); -rl.close(); +```bash +browser-use skill install +``` -// Agent continues -const result = await client.run( - "You are now logged in. Go to the dashboard and export the monthly report", - { sessionId: session.id }, -); -console.log(result.output); -await client.sessions.stop(session.id); +**4. Authenticate for cloud browsers** + +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. ---- +**5. Use it** -## 3. Agent Mail +Claude Code uses its bash tool to run CLI commands directly: -When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` -Agent Mail is **enabled by default** (`agentmail=True`). Each session gets a unique email address (`session.agentmail_email`). The agent can send and receive emails during the task. +For the complete CLI reference and Python execution examples, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Agent Self-Registration -client = AsyncBrowserUse() +Claude Code can provision its own Browser Use API key autonomously — no human interaction needed. The free tier includes unlimited browser hours, free proxies in 195+ countries, persistent browser profiles, CAPTCHA solving, and stealth browsing at zero cost. -result = await client.run( -""" -1. Go to example.com/signup -2. Sign up with the agent's email address (use the email available to you) -3. Check your email inbox for the verification code -4. Enter the code on the website -5. Complete the registration -""", -agentmail=True, # default, shown for clarity -) -print(result.output) +Install the Browser Use CLI and skill: + +```bash +uv tool install browser-use +browser-use skill install ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); +Claude Code can follow the [REST flow](https://docs.browser-use.com/cloud/agent-signup#rest-flow) to request and verify a challenge, then put the returned key in its shell environment: -const result = await client.run( - `1. Go to example.com/signup - 2. Sign up with the agent's email address (use the email available to you) - 3. Check your email inbox for the verification code - 4. Enter the code on the website - 5. Complete the registration`, - { agentmail: true }, // default, shown for clarity -); -console.log(result.output); +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` -### For end-client automation +### Claim the account (optional) -If you're automating on behalf of your users and they need to receive 2FA codes: +If the human wants to see the account in the dashboard later, use the [claim endpoint](https://docs.browser-use.com/cloud/agent-signup#claim-the-account). The returned claim URL is valid for 1 hour. -1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). -2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. +## Pay with USDC via x402 -This way, your client's real email stays private — the agent only sees the forwarded verification emails. +Two ways to use x402 with Browser Use Cloud: -### Connect external email via Composio +- **Top up an existing account** — add credits to your API key (e.g. one created via Agent Self-Registration above) using USDC. No credit card required. Use this when free credits run out. +- **Accountless** — wallet IS the identity, no signup needed. Pure x402 / agent-economy native. Use this for autonomous agents that hold their own wallet. -You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. +Install the skill: ---- +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` -## 4. TOTP secret in prompt +Then in Claude Code: -If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. +``` +> /x402 +``` -When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. +The skill asks whether you have an existing API key (top-up mode) or want accountless mode, then walks you through generating (or importing) an EVM wallet, funding it via Coinbase, and running a verification task. You'll need ~$5 of USDC on Base mainnet. Each top-up is $1. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +For the SDK API and protocol details, see the [x402 guide](https://docs.browser-use.com/cloud/guides/x402). -client = AsyncBrowserUse() -# The TOTP secret from your authenticator setup — NOT the 6-digit code -totp_secret = "JBSWY3DPEHPK3PXP" +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents -result = await client.run( -f""" -Log into example.com with username user@example.com and password mypassword. -When prompted for a 2FA code, generate one using pyotp: -import pyotp -totp = pyotp.TOTP("{totp_secret}") -code = totp.now() +[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents) run on Anthropic's hosted platform. Install the `browser-use` CLI in the agent's environment and it can drive a stealth cloud browser — with proxies, CAPTCHA solving, live view, and recording. Your API key stays in a credential vault; the model never sees it. -Enter the generated code. -""", -) -print(result.output) +The sandbox can't run a local browser, so the agent starts a named Browser Use Cloud browser and drives it with `browser-use <<'PY'` Python snippets. + +## 1. Create an environment + +Pre-install the CLI so it's ready at session start (no runtime install). + +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); +## 2. Create a credential vault -// The TOTP secret from your authenticator setup — NOT the 6-digit code -const totpSecret = "JBSWY3DPEHPK3PXP"; +Store your key as an environment variable so the CLI reads it and the model never does. Get one at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -const result = await client.run( - `Log into example.com with username user@example.com and password mypassword. - When prompted for a 2FA code, generate one using pyotp: +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | - import pyotp - totp = pyotp.TOTP("${totpSecret}") - code = totp.now() +## 3. Create the agent - Enter the generated code.`, -); -console.log(result.output); +Tell it to use the CLI in cloud mode. + +```yaml +name: browser agent +model: + id: claude-opus-4-8 +description: Drives a stealth cloud browser with the Browser Use CLI. +system: | + You are a browser agent. Use the `browser-use` CLI to complete web tasks. + Never launch a local browser in this sandbox. Start a named cloud browser: + browser-use <<'PY' + start_remote_daemon("managed") + PY + Then run browser work through the same name: + BU_NAME=managed browser-use <<'PY' + new_tab("https://example.com") + print(page_info()) + PY + Your BROWSER_USE_API_KEY is in the environment; never print it. +tools: + - type: agent_toolset_20260401 # shell access so the agent can run the CLI + default_config: + enabled: true + permission_policy: + type: always_allow ``` -This works because the Browser Use agent can execute Python code as part of its task. The agent runs `pyotp.TOTP(secret).now()` to generate a time-based 6-digit code, then types it into the 2FA field. +## 4. Start a session and send a task -### Where to find TOTP secrets +The Console only observes; kick the agent off with a `user.message` event. -- **1Password**: Edit item → One-Time Password → Show secret -- **Google Authenticator**: During setup, click "Can't scan it?" to see the key -- **Authy**: Export via desktop app settings -- **Most sites**: Look for "manual entry" or "setup key" during 2FA enrollment +```bash +curl -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: managed-agents-2026-04-01" \ + -H "content-type: application/json" \ + -d '{"events":[{"type":"user.message","content":[{"type":"text", + "text":"Get the top 5 Hacker News stories with their links."}]}]}' +``` ---- +## 5. Watch it run -## Which approach should I use? +The agent starts a named cloud browser, runs Python helper snippets through `browser-use`, then returns the result. The session shows up in [cloud.browser-use.com](https://cloud.browser-use.com) → **Remote Browsers** with a **Live View** and an **mp4 recording**. -Start with **Profiles** — log in once, reuse cookies. If cookies expire frequently, add **TOTP secret in prompt** for fully automated re-login. -Use **Profiles** with one profile per user. For initial login, use **Human in the loop** — your user logs in once via the live view, then the agent reuses the session. For email 2FA, set up **Agent Mail** with email forwarding from your user. -Use **Agent Mail** (enabled by default). For end-client scenarios, have them forward 2FA emails to a dedicated inbox. -Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. + Always use a cloud browser — the Managed Agents sandbox has no GUI, so a local + browser won't start. Cloud mode also gives you stealth, residential proxies, + live view, and recording. # OpenClaw @@ -1801,16 +3303,16 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: ```json5 { browser: { -enabled: true, -defaultProfile: "browser-use", -remoteCdpTimeoutMs: 3000, -remoteCdpHandshakeTimeoutMs: 5000, -profiles: { - "browser-use": { - cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", - color: "#ff750e", - }, -}, + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, }, } ``` @@ -1848,7 +3350,7 @@ The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser a **1. Install the CLI** ```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash +uv tool install browser-use ``` **2. Verify the installation** @@ -1865,269 +3367,141 @@ Paste this setup prompt into your OpenClaw agent: Install or upgrade browser-use with `uv tool install --python 3.12 --upgrade --force 'browser-use @ git+https://github.com/browser-use/browser-use.git'`, run `browser-use skill install`, and connect it to my browser. Follow https://github.com/browser-use/browser-use if setup or connection fails. ``` -Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to navigate pages, click elements, fill forms, take screenshots, extract data, and more. The skill file teaches the agent the full command set. - -For the complete CLI reference and advanced features like cloud browsers, tunnels, sessions, and Python execution, see the [README](https://github.com/browser-use/browser-use/blob/main/browser_use/skill_cli/README.md) and the [Browser Use docs](https://docs.browser-use.com). +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. - -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -``` -https://api.browser-use.com/v3/mcp -``` +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -## Claude Code +[Hermes Agent](https://github.com/nousresearch/hermes-agent) is an open-source, self-improving AI agent by Nous Research. It has built-in browser automation tools that work with local Chromium out of the box. Add Browser Use and those tools run on cloud browsers with anti-detect profiles, residential proxies in 195+ countries, and stealth browsing. -```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp -``` +Two ways to set it up: configure Browser Use as Hermes's cloud browser backend, or install the Browser Use CLI and let Hermes drive it directly. -## Claude Desktop +## Option 1: Cloud Browser Backend -Add to `claude_desktop_config.json`: +Hermes has built-in browser tools (`browser_navigate`, `browser_click`, `browser_snapshot`, etc.) that default to local Chromium. Point them at Browser Use cloud browsers instead — no extra dependencies, same Hermes experience. -```json -{ - "mcpServers": { -"browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} -``` +### Setup -## Cursor +**1. Get your API key** -Add to `.cursor/mcp.json`: +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com) and copy your API key from [Settings → API Keys](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -```json -{ - "mcpServers": { -"browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} -``` +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -## Windsurf +**2. Configure Hermes** -Add to `~/.codeium/windsurf/mcp_config.json`: +Run the setup wizard: -```json -{ - "mcpServers": { -"browser-use": { - "serverUrl": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} +```bash +hermes setup tools ``` -## Available Tools - -| Tool | Description | -|------|-------------| -| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | -| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | -| `send_task` | Send a follow-up task to an idle keep-alive session. | -| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | -| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | -| `list_sessions` | List recent sessions with status and cost. | -| `list_browser_profiles` | List browser profiles for authenticated tasks. | - +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -# Webhooks -Source: https://docs.browser-use.com/cloud/guides/webhooks +Or configure manually — add your key to `~/.hermes/.env`: +```bash +BROWSER_USE_API_KEY=your_key_here +``` -Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). +And set the provider in `~/.hermes/config.yaml`: -## Events +```yaml +browser: + cloud_provider: browser-use +``` -| Event | When | -|-------|------| -| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | -| `test` | Webhook test ping | +**3. Use it** -## Payload +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: -```json -{ - "type": "agent.task.status_update", - "timestamp": "2025-01-15T10:30:00Z", - "payload": { -"task_id": "task_abc123", -"session_id": "session_xyz", -"status": "idle", -"metadata": {} - } -} +``` +> Find the top trending repositories on GitHub today and summarize them ``` -## Signature verification - -Every webhook request includes two headers: +## Option 2: Browser Use CLI -- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload -- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent +The [Browser Use CLI](https://docs.browser-use.com/open-source/browser-use-cli) is a standalone tool that gives Hermes browser automation through terminal commands. Hermes drives the browser directly via its terminal tool, using Browser Harness and Python helpers through the `browser-use` command. -The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. +### Setup -```python Python -import hashlib -import hmac -import json -import time +**1. Install the CLI** -def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: -# Reject requests older than 5 minutes -try: - ts = int(timestamp) -except (ValueError, TypeError): - return False -if abs(time.time() - ts) > 300: - return False -payload = json.loads(body) -message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" -expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() -return hmac.compare_digest(expected, signature) +```bash +uv tool install browser-use ``` -```typescript TypeScript -import { createHmac, timingSafeEqual } from "crypto"; -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { -return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} +**2. Verify the installation** -function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { - // Reject requests older than 5 minutes - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", secret).update(message).digest("hex"); - return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); -} +```bash +browser-use doctor ``` -## Example: Express webhook handler - -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; +**3. Register the skill** -const app = express(); -app.use(express.raw({ type: "application/json" })); +Register the Browser Use skill with the installed CLI: -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; +```bash +browser-use skill install +``` -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { -return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} +Or ask Hermes directly in chat to install it. -app.post("/webhook", (req, res) => { - const signature = req.headers["x-browser-use-signature"] as string; - const timestamp = req.headers["x-browser-use-timestamp"] as string; +**4. Authenticate for cloud browsers** - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { -return res.status(401).send("Request too old"); - } +Authenticate with your API key: - const body = req.body.toString(); - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); +```bash +browser-use auth login +``` - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { -return res.status(401).send("Invalid signature"); - } +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - if (payload.type === "agent.task.status_update") { -const { task_id, status, session_id } = payload.payload; -console.log(`Task ${task_id} is now ${status}`); - } +**5. Use it** - res.status(200).send("OK"); -}); +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: -app.listen(3000); +``` +> Use browser-use to open github.com/trending and summarize the top repos ``` -## Example: FastAPI webhook handler +For the complete CLI reference and Python execution examples, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time +## Agent Self-Registration -app = FastAPI() +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. -WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] +Install the Browser Use CLI and skill: -@app.post("/webhook") -async def handle_webhook(request: Request): -body = await request.body() -signature = request.headers.get("x-browser-use-signature", "") -timestamp = request.headers.get("x-browser-use-timestamp", "") +```bash +uv tool install browser-use +browser-use skill install +``` -# Reject requests older than 5 minutes -try: - ts = int(timestamp) -except (ValueError, TypeError): - raise HTTPException(status_code=401, detail="Invalid timestamp") -if abs(time.time() - ts) > 300: - raise HTTPException(status_code=401, detail="Request too old") +The agent can follow the [REST flow](https://docs.browser-use.com/cloud/agent-signup#rest-flow) to request and verify a challenge, then use the returned API key. + +**Copy the key to Hermes config** -payload = json.loads(body) -message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" -expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() +For the cloud browser backend (Option 1): -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +```bash +hermes config set BROWSER_USE_API_KEY +``` -if payload["type"] == "agent.task.status_update": - task_id = payload["payload"]["task_id"] - status = payload["payload"]["status"] - print(f"Task {task_id} is now {status}") +For CLI mode (Option 2), put the key in the agent's shell environment: -return {"status": "ok"} +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` - For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. +### Claim the account (optional) + +If the human wants to see the account in the dashboard later, use the [claim endpoint](https://docs.browser-use.com/cloud/agent-signup#claim-the-account). The returned claim URL is valid for 1 hour. # n8n @@ -2225,8 +3599,8 @@ import { client } from "./api"; export async function createSession() { const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, + keepAlive: true, + enableRecording: true, }); return { id: session.id, liveUrl: session.liveUrl, status: session.status }; } @@ -2243,7 +3617,7 @@ async function handleSend(message: string) { const session = await createSession(); router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` + `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` ); } ``` @@ -2259,7 +3633,7 @@ const streamTask = useCallback(async (task: string) => { const run = client.run(task, { sessionId }); for await (const msg of run) { -setMessages((prev) => [...prev, msg]); + setMessages((prev) => [...prev, msg]); } // Iterator done — task reached terminal state @@ -2303,7 +3677,7 @@ useEffect(() => { if (!isTerminal) return; client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); + if (urls.length) setRecordingUrls(urls); }); }, [isTerminal, sessionId]); ``` @@ -2331,23 +3705,23 @@ The session page consumes everything through a context provider: ```typescript session/[id]/page.tsx function SessionPage() { const { session, turns, isBusy, isTerminal, recordingUrls, sendMessage, stopTask } = -useSession(); + useSession(); return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
+
+ {/* Chat column */} +
+ + +
+ + {/* Live browser view — liveUrl available from session creation */} + +
); } ``` @@ -2364,6 +3738,116 @@ useSession(); | `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup + + +An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. + +The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. + +## REST flow + +### 1. Request a challenge + +```bash +curl -X POST https://api.browser-use.com/cloud/signup \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +Request body, optional (include a user email/name if available): + +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` + +Response: + +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} +``` + +### 2. Solve the challenge + +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. + +### 3. Verify the answer + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/verify \ + -H "Content-Type: application/json" \ + -d '{"challenge_id":"uuid","answer":"144.00"}' +``` + +Request body: + +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} +``` + +Response: + +```json +{ + "api_key": "bu_..." +} +``` + +Use the returned key for Browser Use Cloud API requests. + +For example, create a browser session: + +```bash +curl -X POST https://api.browser-use.com/api/v3/browsers \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + +## Claim the account + +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." +``` + +Response: + +```json +{ + "claim_url": "https://..." +} +``` + +The claim URL is valid for 1 hour. + +## CLI usage + +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: + +```bash +uv tool install browser-use +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +Replace `bu_...` with the key returned by the REST flow. + + # Grow Therapy provider search Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare @@ -2400,28 +3884,28 @@ const client = new BrowserUse(); ```python Python class Provider(BaseModel): -name: str -title: str -specialties: list[str] -insurance_plans: list[str] -rating: float | None = None -next_available: str | None = None + name: str + title: str + specialties: list[str] + insurance_plans: list[str] + rating: float | None = None + next_available: str | None = None class ProviderSearch(BaseModel): -providers: list[Provider] -total_found: int | None = None -location: str -specialty: str + providers: list[Provider] + total_found: int | None = None + location: str + specialty: str ``` ```typescript TypeScript const ProviderSearch = z.object({ providers: z.array(z.object({ -name: z.string(), -title: z.string(), -specialties: z.array(z.string()), -insurancePlans: z.array(z.string()), -rating: z.number().nullable(), -nextAvailable: z.string().nullable(), + name: z.string(), + title: z.string(), + specialties: z.array(z.string()), + insurancePlans: z.array(z.string()), + rating: z.number().nullable(), + nextAvailable: z.string().nullable(), })), totalFound: z.number().nullable(), location: z.string(), @@ -2442,19 +3926,19 @@ const workspace = await client.workspaces.create({ name: "grow-therapy-search" } ```python Python result = await client.run( -"Go to growtherapy.com and search for therapists in {{New York}} " -"who specialize in {{anxiety}} and accept insurance. " -"Return the first 5 provider profiles as JSON.", -workspace_id=str(workspace.id), -output_schema=ProviderSearch, + "Go to growtherapy.com and search for therapists in {{New York}} " + "who specialize in {{anxiety}} and accept insurance. " + "Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, ) for p in result.output.providers: -print(f"{p.name} ({p.title})") -print(f" Specialties: {', '.join(p.specialties)}") -print(f" Rating: {p.rating}") -print(f" Next available: {p.next_available}") -print() + print(f"{p.name} ({p.title})") + print(f" Specialties: {', '.join(p.specialties)}") + print(f" Rating: {p.rating}") + print(f" Next available: {p.next_available}") + print() ``` ```typescript TypeScript const result = await client.run( @@ -2481,16 +3965,16 @@ locations = ["Los Angeles", "Chicago", "Houston", "Miami"] specialties = ["depression", "trauma", "ADHD"] for location in locations: -for specialty in specialties: - result = await client.run( - f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " - f"who specialize in {{{{{specialty}}}}} and accept insurance. " - f"Return the first 5 provider profiles as JSON.", - workspace_id=str(workspace.id), - output_schema=ProviderSearch, - ) - count = len(result.output.providers) - print(f"{location} / {specialty}: {count} providers found") + for specialty in specialties: + result = await client.run( + f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " + f"who specialize in {{{{{specialty}}}}} and accept insurance. " + f"Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, + ) + count = len(result.output.providers) + print(f"{location} / {specialty}: {count} providers found") ``` ```typescript TypeScript const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; @@ -2498,13 +3982,13 @@ const specialties = ["depression", "trauma", "ADHD"]; for (const location of locations) { for (const specialty of specialties) { -const result = await client.run( - `Go to growtherapy.com and search for therapists in {{${location}}} ` + - `who specialize in {{${specialty}}} and accept insurance. ` + - `Return the first 5 provider profiles as JSON.`, - { workspaceId: workspace.id, schema: ProviderSearch }, -); -console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); + const result = await client.run( + `Go to growtherapy.com and search for therapists in {{${location}}} ` + + `who specialize in {{${specialty}}} and accept insurance. ` + + `Return the first 5 provider profiles as JSON.`, + { workspaceId: workspace.id, schema: ProviderSearch }, + ); + console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); } } ``` @@ -2589,7 +4073,6 @@ Source: https://docs.browser-use.com/cloud/legacy/agent | Model | API String | Cost per Step | | ----- | ---------- | ------------- | | Browser Use 2.0 (default) | `browser-use-2.0` | \$0.006 | -| Browser Use LLM | `browser-use-llm` | \$0.002 | | O3 | `o3` | \$0.03 | | Gemini Flash Latest | `gemini-flash-latest` | \$0.0075 | | Gemini Flash Lite Latest | `gemini-flash-lite-latest` | \$0.005 | @@ -2623,15 +4106,15 @@ client = AsyncBrowserUse() session = await client.sessions.create() upload = await client.files.session_url( -session.id, -file_name="input.pdf", -content_type="application/pdf", -size_bytes=1024, + session.id, + file_name="input.pdf", + content_type="application/pdf", + size_bytes=1024, ) with open("input.pdf", "rb") as f: -async with httpx.AsyncClient() as http: - await http.post(upload.url, content=f.read(), headers={"Content-Type": "application/pdf"}) + async with httpx.AsyncClient() as http: + await http.post(upload.url, content=f.read(), headers={"Content-Type": "application/pdf"}) result = await client.run("Summarize the uploaded PDF", session_id=session.id) ``` @@ -2664,8 +4147,8 @@ const result = await client.run("Summarize the uploaded PDF", { sessionId: sessi ```python Python result = await client.tasks.get(task_id) for file in result.output_files: -output = await client.files.task_output(task_id, file.id) -print(output.download_url) # download URL + output = await client.files.task_output(task_id, file.id) + print(output.download_url) # download URL ``` ```typescript TypeScript const result = await client.tasks.get(taskId); @@ -2687,8 +4170,8 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() run = client.run("Find the most upvoted post on Reddit r/technology today") async for step in run: -print(f"Step {step.number}: {step.next_goal}") -print(f" URL: {step.url}") + print(f"Step {step.number}: {step.next_goal}") + print(f" URL: {step.url}") print(run.result.output) # final result after iteration ``` @@ -2769,8 +4252,8 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() skill = await client.skills.create( -goal="Extract the top X posts from HackerNews. For each post return: title, URL, score, author, comment count, and rank. X is an input parameter.", -agent_prompt="Go to https://news.ycombinator.com, click on the first post to load its content, go back to the list, and scroll down to trigger loading of additional posts.", + goal="Extract the top X posts from HackerNews. For each post return: title, URL, score, author, comment count, and rank. X is an input parameter.", + agent_prompt="Go to https://news.ycombinator.com, click on the first post to load its content, go back to the list, and scroll down to trigger loading of additional posts.", ) print(skill.id) ``` @@ -2791,8 +4274,8 @@ Skill creation takes ~30 seconds. You can also create skills visually from the [ ```python Python result = await client.skills.execute( -skill.id, -parameters={"X": 10}, + skill.id, + parameters={"X": 10}, ) print(result) ``` @@ -2864,9 +4347,9 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Log into my Jira account and create a new ticket", -op_vault_id="your-vault-id", -allowed_domains=["*.atlassian.net"], + "Log into my Jira account and create a new ticket", + op_vault_id="your-vault-id", + allowed_domains=["*.atlassian.net"], ) print(result.output) ``` @@ -2877,8 +4360,8 @@ const client = new BrowserUse(); const result = await client.run( "Log into my Jira account and create a new ticket", { -opVaultId: "your-vault-id", -allowedDomains: ["*.atlassian.net"], + opVaultId: "your-vault-id", + allowedDomains: ["*.atlassian.net"], }, ); console.log(result.output); @@ -2888,17 +4371,17 @@ For SSO/OAuth redirects, include all required domains: ```python Python result = await client.run( -"Log into Jira and create a ticket for the Q4 release", -op_vault_id="your-vault-id", -allowed_domains=["*.atlassian.net", "*.okta.com"], + "Log into Jira and create a ticket for the Q4 release", + op_vault_id="your-vault-id", + allowed_domains=["*.atlassian.net", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into Jira and create a ticket for the Q4 release", { -opVaultId: "your-vault-id", -allowedDomains: ["*.atlassian.net", "*.okta.com"], + opVaultId: "your-vault-id", + allowedDomains: ["*.atlassian.net", "*.okta.com"], }, ); ``` @@ -2926,9 +4409,9 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Log into GitHub and star the browser-use/browser-use repo", -secrets={"github.com": "username:password123"}, -allowed_domains=["github.com"], + "Log into GitHub and star the browser-use/browser-use repo", + secrets={"github.com": "username:password123"}, + allowed_domains=["github.com"], ) ``` ```typescript TypeScript @@ -2938,8 +4421,8 @@ const client = new BrowserUse(); const result = await client.run( "Log into GitHub and star the browser-use/browser-use repo", { -secrets: { "github.com": "username:password123" }, -allowedDomains: ["github.com"], + secrets: { "github.com": "username:password123" }, + allowedDomains: ["github.com"], }, ); ``` @@ -2950,28 +4433,85 @@ For SSO/OAuth redirects, include all domains in the auth flow: ```python Python result = await client.run( -"Log into the company portal and download the Q4 report", -secrets={ - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowed_domains=["portal.example.com", "*.okta.com"], + "Log into the company portal and download the Q4 report", + secrets={ + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowed_domains=["portal.example.com", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into the company portal and download the Q4 report", { -secrets: { - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowedDomains: ["portal.example.com", "*.okta.com"], + secrets: { + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowedDomains: ["portal.example.com", "*.okta.com"], }, ); ``` +# API Reference +Source: https://docs.browser-use.com/cloud/api-v4-overview + + +## Authentication + +All requests require an API key in the `X-Browser-Use-API-Key` header: + +``` +X-Browser-Use-API-Key: bu_your_key_here +``` + +Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. + +## Base URL + +``` +https://api.browser-use.com/api/v4 +``` + +## The core loop + +Create a run, poll its status until terminal, then fetch the full result. `status` is a cheap indexed lookup — poll it, not the full run. + +```bash Create a run +curl -X POST https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"task": "Find the top 3 trending repos on GitHub today"}' +``` + +```bash Poll status until completed | failed | cancelled (replace RUN_ID) +curl https://api.browser-use.com/api/v4/runs/RUN_ID/status \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +```bash Fetch the full run once it's terminal +curl https://api.browser-use.com/api/v4/runs/RUN_ID \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +## Sessions and follow-ups + +A run belongs to a session (a conversation). Send a follow-up message to a session's queue — it runs as the next turn, or immediately with `interrupt: true`: + +```bash Queue a follow-up (replace SESSION_ID) +curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"text": "Now open the top result", "interrupt": false}' +``` + +## SDKs + +The [Cloud SDK](https://docs.browser-use.com/cloud/sdk) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. + + # API Reference Source: https://docs.browser-use.com/cloud/api-reference @@ -3061,65 +4601,3 @@ pip install browser-use-sdk npm install browser-use-sdk ``` - -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 67bb5b51..66c54da2 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -22,11 +22,14 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started +- [Introduction](https://docs.browser-use.com/cloud/introduction): One API for AI browser agents and stealth cloud browsers. Choose how much control you want. +- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud): The library and the cloud are different products that combine. Here's which one you want. - [Quick start](https://docs.browser-use.com/cloud/quickstart): State-of-the-art AI browser automation with stealth browsers, CAPTCHA solving, residential proxies, and managed infrastructure. -- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How an AI agent can complete the Browser Use agent challenge to get a free account and API key. +- [Pricing & free tier](https://docs.browser-use.com/cloud/pricing): Browser Use Cloud plans, the free tier (no card required), and every usage-based rate. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent +- [Overview](https://docs.browser-use.com/cloud/agent/overview): The hosted agent takes a task in plain language and drives a stealth browser until it's done. - [Introduction](https://docs.browser-use.com/cloud/agent/quickstart): Easiest way to automate the web. Tell this agent in natural language what it should do, and it can interact with the web like a human. - [Models](https://docs.browser-use.com/cloud/agent/models): Choose the right model for your task. - [Structured output](https://docs.browser-use.com/cloud/agent/structured-output): Get validated, typed data back from agent tasks. @@ -35,29 +38,47 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Upload files for the agent, download files the agent creates. - [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Run a task once, then re-execute it for $0 LLM cost. - [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Let a human interact with the live browser while the agent is running. Useful for approvals, payments, complex auth flows, or reviewing agent work before continuing. +- [Performance & speed](https://docs.browser-use.com/cloud/agent/performance): Every setting that affects agent latency, from model choice to typing speed. ## Browser +- [Overview](https://docs.browser-use.com/cloud/browser/overview): Remote stealth browsers you control over CDP. What they are and when to use one. +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create): Every way to start a cloud browser: SDK, REST, or a single WebSocket URL, with all parameters and the response schema. +- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions): Session lifecycle: states, timeouts, stopping, disconnect behavior, and what you're billed for. - [Introduction Stealth](https://docs.browser-use.com/cloud/browser/stealth): Best stealth on the planet. We fork Chromium to give agents access to all websites. +- [CAPTCHA Solving](https://docs.browser-use.com/cloud/browser/captcha): Browser Use remote browsers solve CAPTCHAs automatically, on by default, on every plan. - [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Residential proxies in 195+ countries. On by default. +- [Screenshots](https://docs.browser-use.com/cloud/browser/screenshots): Take viewport and full-page screenshots from a cloud browser session, and control where they're saved. - [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview): Watch the agent's browser in real time. Embed it in your app. -- [Playwright, Puppeteer, Selenium](https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium): Connect your automation framework to Browser Use's stealth infrastructure via CDP. +- [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent): Run the open-source Browser Use agent on a cloud stealth browser. Your code, our infrastructure. + +## Automation frameworks +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright): Connect Playwright to a remote stealth browser over CDP — Python and TypeScript. +- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer): Connect Puppeteer to a remote stealth browser with browserWSEndpoint. +- [Selenium](https://docs.browser-use.com/cloud/browser/selenium): Run Selenium-style automation on Browser Use's stealth browsers — and why to bridge through CDP. ## Authentication - [Profiles](https://docs.browser-use.com/cloud/guides/authentication): Persistent browser state — cookies, localStorage, saved passwords. Login once, reuse across sessions. -- [Sync local and cloud cookies](https://docs.browser-use.com/cloud/guides/profile-sync): Sync your local browser cookies to the cloud — instantly authenticate without managing credentials. +- [Profiles / Cookie sync](https://docs.browser-use.com/cloud/guides/profile-sync): Profiles carry cookies and login state across sessions — sync them from your local browser or reuse them in the cloud. - [2FA](https://docs.browser-use.com/cloud/guides/2fa): Best practices for handling two-factor authentication in automated browser sessions. ## More - [FAQ](https://docs.browser-use.com/cloud/faq): Common questions and solutions. -## Integrations -- [OpenClaw](https://docs.browser-use.com/cloud/tutorials/integrations/openclaw): Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill. +## Platform features - [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. - [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. +- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. + +## Integrations +- [Claude Code](https://docs.browser-use.com/cloud/tutorials/integrations/claude-code): Give Claude Code cloud browser automation with Browser Use. +- [Claude Managed Agents](https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents): Give Anthropic's Claude Managed Agents a stealth cloud browser via the Browser Use CLI. +- [OpenClaw](https://docs.browser-use.com/cloud/tutorials/integrations/openclaw): Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. - [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. ## Tutorials - [Chat UI](https://docs.browser-use.com/cloud/tutorials/chat-ui): Full end-to-end example. Build a chat UI with live browser preview, follow-up tasks, recording, and streaming messages. +- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. - [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) @@ -67,70 +88,12 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [1Password & 2FA](https://docs.browser-use.com/cloud/guides/1password): Auto-fill passwords and TOTP codes from 1Password during agent tasks. - [Secrets](https://docs.browser-use.com/cloud/guides/secrets): Pass domain-scoped credentials to the agent securely. +## API v4 +- [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. + ## API v3 - [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 12704f34..24d73b42 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -3,6 +3,84 @@ > Machine-readable OpenAPI spec: https://docs.browser-use.com/openapi.json (v3, canonical — also at /cloud/openapi/v3.json; legacy v2: /cloud/openapi/v2.json). Dashboard: https://cloud.browser-use.com. Create an API key: https://cloud.browser-use.com/settings?tab=api-keys&new=1 +# Introduction +Source: https://docs.browser-use.com/cloud/introduction + + +Browser Use Cloud gives you two things with one API key: + +1. **An AI agent** that completes web tasks from a plain-language prompt. You describe the outcome, the agent drives the browser. +2. **Stealth cloud browsers** you drive yourself over CDP with Playwright, Puppeteer, or any automation framework. + +Every browser, in both modes, runs a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with anti-fingerprinting, [automatic CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default. + +## Which one do you want? + +**Use the [Agent](https://docs.browser-use.com/cloud/agent/overview)** when the task is the point and the clicks are not. Price monitoring, form filling, research, multi-step workflows described in a sentence: + +```python +result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +``` + +**Use a [Browser](https://docs.browser-use.com/cloud/browser/overview)** when you want deterministic control. You have Playwright or Puppeteer scripts already, you need exact selectors and timing, or you're building your own agent: + +```python +browser = await client.browsers.create() +# connect with Playwright over CDP via browser.cdp_url +``` + +The two combine. An agent can run inside a browser session you created, and you can [connect Playwright to the browser behind an agent task](https://docs.browser-use.com/cloud/browser/playwright) for hybrid workflows: the agent handles fuzzy steps, your code handles deterministic ones. + +## Already using the open-source library? + +The [Browser Use library](/open-source/introduction) runs agents on your machine with your own LLM keys. Cloud adds managed stealth browsers and a hosted agent on top. You can keep your local agent code and point it at a cloud browser, see [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud). + +## Start here + +- [Quickstart](https://docs.browser-use.com/cloud/quickstart) — first task in five minutes +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — every creation method and parameter +- [Pricing](https://docs.browser-use.com/cloud/pricing) — what costs what + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — accuracy and stealth results vs other providers +- [The ultimate guide to web scraping (2026)](https://browser-use.com/posts/web-scraping-guide-2026) + + +# Open source vs Cloud +Source: https://docs.browser-use.com/cloud/open-source-vs-cloud + + +Browser Use is two things with one name. The confusion is common enough to deserve its own page. + +**The [open-source library](/open-source/introduction)** (`pip install browser-use`) is an agent framework that runs on your machine. You bring your own LLM key, it launches a local Chromium, and nothing leaves your infrastructure. Free, Apache-licensed, yours. + +**Browser Use Cloud** (this documentation) is a paid API with two services: [stealth cloud browsers](https://docs.browser-use.com/cloud/browser/overview) you can drive with any framework, and a [hosted agent](https://docs.browser-use.com/cloud/agent/overview) that runs tasks for you, no library install required. + +## Which do you want? + +| You want to... | Use | +|---|---| +| Run an agent locally, free, with your own LLM keys | Open source library | +| Keep your local agent but stop getting blocked by websites | Library + [cloud browser](https://docs.browser-use.com/cloud/browser/open-source-agent) | +| Drive stealth browsers with existing Playwright/Puppeteer scripts | Cloud [Browser](https://docs.browser-use.com/cloud/browser/overview) | +| Send a task and get a result, zero infrastructure | Cloud [Agent](https://docs.browser-use.com/cloud/agent/overview) | + +## Common questions + +**Do I need an API key to use the library?** +No. The library needs an LLM provider key (OpenAI, Anthropic, Ollama for fully local). A Browser Use API key is only needed for cloud features. + +**Does the library have stealth or CAPTCHA solving?** +No. Those are properties of the cloud browsers. The bridge is one parameter: point the library's `Browser` at a [cloud browser session](https://docs.browser-use.com/cloud/browser/open-source-agent). + +**Is the cloud agent the same agent as the library?** +The cloud agent is the hosted, managed version, with the same task-in, result-out model plus cloud-only features like [structured output](https://docs.browser-use.com/cloud/agent/structured-output), [workspaces](https://docs.browser-use.com/cloud/agent/workspaces), and [human-in-the-loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop). + +**Can I self-host the cloud?** +The library is the self-hosted option: your machines, your browsers, your keys. The stealth browser fleet and hosted agent are not self-hostable. + + # Quick start Source: https://docs.browser-use.com/cloud/quickstart @@ -29,9 +107,9 @@ import asyncio from browser_use_sdk.v3 import AsyncBrowserUse async def main(): -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) + client = AsyncBrowserUse() + result = await client.run("List the top 20 posts on Hacker News today with their points") + print(result.output) asyncio.run(main()) ``` @@ -67,113 +145,122 @@ Want a full working app? Check out the [Chat UI example](https://docs.browser-us If you are an LLM, read/include [docs.browser-use.com/llms-full.txt](https://docs.browser-use.com/llms-full.txt) — it contains the complete SDK reference with all code examples in a single file optimized for LLMs. For a shorter index: [docs.browser-use.com/llms.txt](https://docs.browser-use.com/llms.txt). -# Prompt for Vibecoders -Source: https://docs.browser-use.com/cloud/vibecoding +# Pricing & free tier +Source: https://docs.browser-use.com/cloud/pricing -Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsurf, etc.) — it contains all the context needed to build with Browser Use. +Browser Use Cloud has a free tier and four paid plans. Usage (browser time, proxy data, agent tokens) is billed on top of the plan. The [pricing page](https://browser-use.com/pricing) is the canonical source; the numbers here are kept in sync with it. -``` -https://docs.browser-use.com/cloud/llms.txt -``` +## Free tier +Free, no credit card required. It includes: -# Agent Sign Up for Browser Use -Source: https://docs.browser-use.com/cloud/agent-signup +- 3 concurrent browser sessions +- 1 browser profile, 1 team member +- Basic proxy pool +- Advanced [stealth](https://docs.browser-use.com/cloud/browser/stealth), [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and [webhook events](https://docs.browser-use.com/cloud/guides/webhooks) +- Community support +Stealth and CAPTCHA solving are on for every tier, including free. They are not paid add-ons. -An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. +## Plans -The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. +| Plan | Monthly | Included credits | Concurrent sessions | Team members | +|------|---------|------------------|---------------------|--------------| +| Free | $0 | — | 3 | 1 | +| Dev | $29 | $29 | 25 | 5 | +| Business | $299 | $299 | 200 | Unlimited | +| Scaleup | $999 | $999 | 500 | Unlimited | +| Enterprise | Custom | Negotiated | Negotiated | Negotiated | -## REST flow +Annual billing is pay for 10 months, get 12 (Dev $290/yr, Business $2,990/yr, Scaleup $9,990/yr). -### 1. Request a challenge +The concurrent-session limit is what a `429 Too many concurrent active sessions` error refers to. Stop idle sessions or upgrade the plan to raise it. -```bash -curl -X POST https://api.browser-use.com/cloud/signup \ - -H "Content-Type: application/json" \ - -d '{}' -``` +## Usage rates -Request body, optional (include a user email/name if available): +Billed against your plan credits, then charged if you exceed them. -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` +**Browser & proxies** +- Browser session: $0.02/hour, active time only +- Proxy bandwidth: $5/GB -Response: +**Agent (v3, token-based at 1.2× provider rates)** -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` +| Model | Input / output per 1M tokens | +|-------|------------------------------| +| GPT-5.4 Mini | $0.90 / $5.40 | +| Claude Sonnet 4.6 | $3.60 / $18.00 | +| Claude Opus 4.6 / 4.7 | $6.00 / $30.00 | -### 2. Solve the challenge +When you [bring your own LLM](https://docs.browser-use.com/cloud/agent/models), those tokens go to your own provider instead. -Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. +**Browser Use Box** (persistent remote agent environment, billed while running) +- Small: $1/day — 2 vCPU, 4 GB RAM, 20 GB +- Medium: $2/day — 2 vCPU, 8 GB RAM, 50 GB +- Large: $4/day — 4 vCPU, 16 GB RAM, 100 GB -### 3. Verify the answer +## Tracking spend -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` +Every browser session object reports its own running cost (`browserCost`, `proxyCost`, `proxyUsedMb`), so spend is inspectable per session. [Stop sessions](https://docs.browser-use.com/cloud/browser/sessions#stopping-a-session) when done — an idle session bills until it stops or [times out](https://docs.browser-use.com/cloud/browser/sessions#timeouts). -Request body: +## The open-source library is free -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` +The [library](/open-source/introduction) has no Browser Use charges — you pay only your own LLM provider. Cloud rates apply when you use cloud browsers or the hosted agent. See [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud). -Response: +## Further reading -```json -{ - "api_key": "bu_..." -} -``` +- [Remote browsers for agents: the Browser Use free tier](https://browser-use.com/posts/free-tier-announcement) +- [How we made cloud browsers 3x cheaper and faster](https://browser-use.com/posts/firecracker-browser-infra) -Use the returned key for Browser Use Cloud API requests. +{/* TEAM REVIEW: keep this table in sync with browser-use.com/pricing. The v2 agent (per-step from $0.006, task init $0.01) is legacy — omitted here; add a legacy note if v2 users need it. */} -For example, create a browser session: -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' +# Prompt for Vibecoders +Source: https://docs.browser-use.com/cloud/vibecoding + + +Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsurf, etc.) — it contains all the context needed to build with Browser Use. + +``` +https://docs.browser-use.com/cloud/llms.txt ``` -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). -## Claim the account +# Overview +Source: https://docs.browser-use.com/cloud/agent/overview -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` +The agent is a hosted loop: it reads the page, decides an action, executes it, and repeats until the task is complete. You send a task, you get a result. -Response: +```python +from browser_use_sdk import BrowserUse -```json -{ - "claim_url": "https://..." -} +client = BrowserUse() +result = client.run("List the top 5 posts on Hacker News with their points") +print(result.output) ``` -The claim URL is valid for 1 hour. +Each run gets its own [stealth cloud browser](https://docs.browser-use.com/cloud/browser/stealth) with proxies and CAPTCHA handling already on. No browser management, no selectors, no waiting logic. + +## When to use the agent + +The agent fits tasks where you care about the outcome, not the exact clicks: data extraction from sites that change layout, workflows across several pages, form submission, or anything you'd rather describe than script. If you need pixel-exact control or deterministic repetition, drive a [browser session](https://docs.browser-use.com/cloud/browser/overview) directly instead, or record an agent run once and replay it with [cache scripts](https://docs.browser-use.com/cloud/agent/cache-script). + +## What the agent can do + +- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output) — get results as typed JSON matching your schema +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue in the same browser with context intact +- [Streaming](https://docs.browser-use.com/cloud/agent/streaming) — watch steps as they happen +- [Workspaces](https://docs.browser-use.com/cloud/agent/workspaces) — files the agent reads and writes during a run +- [Human-in-the-loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) — take over the browser mid-task, then hand back +- [Models](https://docs.browser-use.com/cloud/agent/models) — pick the LLM that drives the loop +- [Cache scripts](https://docs.browser-use.com/cloud/agent/cache-script) — record a run, replay it without LLM calls + +## Next + +Start with the [agent quickstart](https://docs.browser-use.com/cloud/agent/quickstart). For latency and cost tuning, see [Performance & speed](https://docs.browser-use.com/cloud/agent/performance). + # Introduction Source: https://docs.browser-use.com/cloud/agent/quickstart @@ -238,8 +325,8 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", + "List the top 20 posts on Hacker News today with their points", + model="claude-sonnet-4.6", ) print(result.output) ``` @@ -260,6 +347,44 @@ curl -X POST https://api.browser-use.com/api/v3/sessions \ -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6"}' ``` +## Bring your own key + +Connect your own Anthropic, OpenAI, or Google API key. You pay your provider directly + a 0.2× orchestration fee on provider list token prices. + +1. Add your provider key in the dashboard under **Settings → API Keys → Bring Your Own Key**. +2. Pass `use_own_key=True` on the session: + +```python Python +result = await client.run( + "List the top 20 posts on Hacker News today with their points", + model="claude-sonnet-4.6", + use_own_key=True, +) +``` +```typescript TypeScript +const result = await client.run( + "List the top 20 posts on Hacker News today with their points", + { model: "claude-sonnet-4.6", useOwnKey: true }, +); +``` +```bash curl +curl -X POST https://api.browser-use.com/api/v3/sessions \ + -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6", "useOwnKey": true}' +``` + +To default every session on a client to BYOK, set it once on the constructor: + +```python Python +client = AsyncBrowserUse(use_own_key=True) +``` +```typescript TypeScript +const client = new BrowserUse({ useOwnKey: true }); +``` + +The provider key on your project must match the model you pick — Claude models use your Anthropic key, GPT models use your OpenAI key, Gemini models use your Google key. + # Structured output Source: https://docs.browser-use.com/cloud/agent/structured-output @@ -274,20 +399,20 @@ from browser_use_sdk.v3 import AsyncBrowserUse from pydantic import BaseModel class Post(BaseModel): -name: str -points: int -comments: int + name: str + points: int + comments: int class HNPosts(BaseModel): -posts: list[Post] + posts: list[Post] client = AsyncBrowserUse() result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, + "List the top 20 posts on Hacker News today with their points", + output_schema=HNPosts, ) for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") + print(f"{post.name} ({post.points} pts, {post.comments} comments)") ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -329,12 +454,12 @@ client = AsyncBrowserUse() session = await client.sessions.create() result1 = await client.run( -"Go to amazon.com, search for laptops, and open the first result", -session_id=session.id, + "Go to amazon.com, search for laptops, and open the first result", + session_id=session.id, ) result2 = await client.run( -"Extract the customer reviews", -session_id=session.id, + "Extract the customer reviews", + session_id=session.id, ) await client.sessions.stop(session.id) @@ -377,7 +502,7 @@ client = AsyncBrowserUse() run = client.run("Find the top story on Hacker News") async for msg in run: -print(f"[{msg.role}] {msg.summary}") + print(f"[{msg.role}] {msg.summary}") print(run.result.output) ``` @@ -410,17 +535,17 @@ Use `stop(strategy="task")` to cancel the current task without destroying the se ```python Python run = client.run("Find the top story on Hacker News") async for msg in run: -if should_cancel(): - await client.sessions.stop(run.session_id, strategy="task") - break + if should_cancel(): + await client.sessions.stop(run.session_id, strategy="task") + break # Session is now idle — send a different task or close it ``` ```typescript TypeScript const run = client.run("Find the top story on Hacker News"); for await (const msg of run) { if (shouldCancel()) { -await client.sessions.stop(run.sessionId!, { strategy: "task" }); -break; + await client.sessions.stop(run.sessionId!, { strategy: "task" }); + break; } } // Session is now idle — send a different task or close it @@ -441,15 +566,15 @@ session = await client.sessions.create(task="Find the top story on Hacker News") cursor = None while True: -msgs = await client.sessions.messages(session.id, after=cursor, limit=100) -for m in msgs.messages: - print(f"[{m.role}] {m.summary}") - cursor = m.id + msgs = await client.sessions.messages(session.id, after=cursor, limit=100) + for m in msgs.messages: + print(f"[{m.role}] {m.summary}") + cursor = m.id -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) + s = await client.sessions.get(session.id) + if s.status.value in ("idle", "stopped", "error", "timed_out"): + break + await asyncio.sleep(2) print(s.output) ``` @@ -465,14 +590,14 @@ let cursor: string | undefined; while (true) { const msgs = await client.sessions.messages(session.id, { after: cursor, limit: 100 }); for (const m of msgs.messages) { -console.log(`[${m.role}] ${m.summary}`); -cursor = m.id; + console.log(`[${m.role}] ${m.summary}`); + cursor = m.id; } const s = await client.sessions.get(session.id); if (["idle", "stopped", "error", "timed_out"].includes(s.status)) { -console.log(s.output); -break; + console.log(s.output); + break; } await new Promise((r) => setTimeout(r, 2000)); } @@ -506,8 +631,8 @@ await client.workspaces.upload(workspace.id, "people.csv") # Agent can now read it result = await client.run( -"Read people.csv and tell me who works at Google", -workspace_id=workspace.id, + "Read people.csv and tell me who works at Google", + workspace_id=workspace.id, ) print(result.output) ``` @@ -547,8 +672,8 @@ workspace = await client.workspaces.create(name="my-workspace") # Agent creates a file result = await client.run( -"Go to Hacker News and save the top 3 posts as posts.json", -workspace_id=workspace.id, + "Go to Hacker News and save the top 3 posts as posts.json", + workspace_id=workspace.id, ) # Download a single file @@ -557,7 +682,7 @@ await client.workspaces.download(workspace.id, "posts.json", to="./posts.json") # Or download everything paths = await client.workspaces.download_all(workspace.id, to="./output") for p in paths: -print(f"Downloaded: {p}") + print(f"Downloaded: {p}") ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -588,7 +713,7 @@ workspace = await client.workspaces.get(workspace_id) updated = await client.workspaces.update(workspace_id, name="renamed") response = await client.workspaces.list() for w in response.items: -print(w.id, w.name) + print(w.id, w.name) await client.workspaces.delete(workspace_id) ``` ```typescript TypeScript @@ -612,7 +737,7 @@ await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") # List files in a subdirectory files = await client.workspaces.files(workspace.id, prefix="reports/") for f in files.files: -print(f.path, f.size) + print(f.path, f.size) # Download only files from a subdirectory await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") @@ -637,7 +762,7 @@ await client.workspaces.downloadAll(workspace.id, { to: "./output", prefix: "rep # List all files files = await client.workspaces.files(workspace.id) for f in files.files: -print(f.path, f.size) + print(f.path, f.size) # Delete a single file await client.workspaces.delete_file(workspace.id, path="old-report.pdf") @@ -686,14 +811,14 @@ workspace = await client.workspaces.create(name="my-scraper") # First call — agent explores, creates script (~$0.10, ~60s) result = await client.run( -"Get the top @{{5}} stories from https://news.ycombinator.com as JSON", -workspace_id=str(workspace.id), + "Get the top @{{5}} stories from https://news.ycombinator.com as JSON", + workspace_id=str(workspace.id), ) # Second call — cached script, different param ($0 LLM, ~5s) result2 = await client.run( -"Get the top @{{10}} stories from https://news.ycombinator.com as JSON", -workspace_id=str(workspace.id), + "Get the top @{{10}} stories from https://news.ycombinator.com as JSON", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -755,17 +880,17 @@ Run once, then loop over different keywords at $0 LLM each: ```python Python # Agent figures out how to scrape intro.co on first call result = await client.run( -"Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", -workspace_id=str(workspace.id), + "Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", + workspace_id=str(workspace.id), ) # Instant reruns with different keywords for keyword in ["CEO", "marketing", "finance", "e-commerce"]: -result = await client.run( - f"Go to @{{{{https://intro.co/marketplace}}}} and get all @{{{{{keyword}}}}} experts as JSON", - workspace_id=str(workspace.id), -) -print(f"{keyword}: {result.output}, LLM cost: ${result.llm_cost_usd}") + result = await client.run( + f"Go to @{{{{https://intro.co/marketplace}}}} and get all @{{{{{keyword}}}}} experts as JSON", + workspace_id=str(workspace.id), + ) + print(f"{keyword}: {result.output}, LLM cost: ${result.llm_cost_usd}") ``` ```typescript TypeScript // Agent figures out how to scrape intro.co on first call @@ -777,8 +902,8 @@ let result = await client.run( // Instant reruns with different keywords for (const keyword of ["CEO", "marketing", "finance", "e-commerce"]) { result = await client.run( -`Go to @{{https://intro.co/marketplace}} and get all @{{${keyword}}} experts as JSON`, -{ workspaceId: workspace.id }, + `Go to @{{https://intro.co/marketplace}} and get all @{{${keyword}}} experts as JSON`, + { workspaceId: workspace.id }, ); console.log(`${keyword}: ${result.output}`); } @@ -790,14 +915,14 @@ Append empty brackets `@{{}}` to signal "cache this exact task": ```python Python result = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), + "Get the current Bitcoin price from coinmarketcap.com @{{}}", + workspace_id=str(workspace.id), ) # Same task again — cached result2 = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), + "Get the current Bitcoin price from coinmarketcap.com @{{}}", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -817,14 +942,14 @@ result = await client.run( ```python Python result = await client.run( -"Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", -workspace_id=str(workspace.id), + "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", + workspace_id=str(workspace.id), ) # Different countries — cached result2 = await client.run( -"Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", -workspace_id=str(workspace.id), + "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", + workspace_id=str(workspace.id), ) ``` ```typescript TypeScript @@ -845,16 +970,16 @@ result = await client.run( ```python Python # Force-enable without brackets result = await client.run( -"Get the top stories from Hacker News", -workspace_id=str(workspace.id), -cache_script=True, + "Get the top stories from Hacker News", + workspace_id=str(workspace.id), + cache_script=True, ) # Force-disable even with brackets result = await client.run( -"Explain what @{{templates}} means in Jinja", -workspace_id=str(workspace.id), -cache_script=False, + "Explain what @{{templates}} means in Jinja", + workspace_id=str(workspace.id), + cache_script=False, ) ``` ```typescript TypeScript @@ -878,7 +1003,7 @@ You can download and inspect the scripts the agent created: ```python Python files = await client.workspaces.files(workspace.id, prefix="scripts/") for f in files.files: -print(f"{f.path} ({f.size} bytes)") + print(f"{f.path} ({f.size} bytes)") # Download a script to inspect it await client.workspaces.download(workspace.id, "scripts/a7f3b2c1.py", to="./my_script.py") @@ -953,8 +1078,8 @@ print(f"Live view: {session.live_url}") # 2. Agent does the first part result = await client.run( -"Go to amazon.com and search for noise cancelling headphones", -session_id=session.id, + "Go to amazon.com and search for noise cancelling headphones", + session_id=session.id, ) print(result.output) @@ -963,8 +1088,8 @@ input("Press Enter after you've selected a product in the live view...") # 4. Agent continues where the human left off result = await client.run( -"Get the details of the selected product — name, price, and rating", -session_id=session.id, + "Get the details of the selected product — name, price, and rating", + session_id=session.id, ) print(result.output) @@ -1008,133 +1133,519 @@ await client.sessions.stop(session.id); -# Introduction Stealth -Source: https://docs.browser-use.com/cloud/browser/stealth +# Performance & speed +Source: https://docs.browser-use.com/cloud/agent/performance -See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). +Agent latency has four sources, in descending order of impact: LLM inference per step, number of steps, page-load waits, and input mechanics. Tune them in that order. -## What's included +## Model choice -Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. +The model runs on every step, so it dominates end-to-end time. Smaller models cut per-step latency at some cost in reliability on hard pages. See [Models](https://docs.browser-use.com/cloud/agent/models) for the current lineup and speed characteristics. -- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors. -- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster. -- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services. +## Fewer, cheaper steps -## Residential proxies +- **`use_thinking`** — disables the model's extended reasoning per step. +- **`use_judge`** — the post-task quality evaluator; disabling it saves a final LLM call when you don't need verification. +- **`use_vision`** — controls whether screenshots are sent to the model; text-only steps are faster and cheaper, at the cost of visual grounding. +- **`max_history_items`** — caps how much history is resent each step; smaller history means smaller prompts. -Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. +{/* TEAM REVIEW: confirm which of these parameters are exposed on cloud v3/v4 runs vs library-only, and document defaults per surface. Evals found them documented only in legacy v2 docs — flash_mode was removed as inaccurate, verify the rest are current before publish. */} +## Waits and page loads -# Proxies -Source: https://docs.browser-use.com/cloud/browser/proxies +The agent waits between actions and for pages to settle. In the library these are browser-level settings (`wait_between_actions`, `minimum_wait_page_load_time`, `wait_for_network_idle_page_load_time`); lowering them speeds up action-dense tasks on fast sites and risks acting before slow pages are ready. +{/* TEAM REVIEW: reconcile the documented default for wait_between_actions (docs said 0.5s, source says 0.1s) and state the correct values here. */} -A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. +## Typing speed -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +Text is typed character by character through CDP with a small fixed delay per keystroke. This is deliberate: instant-fill is a bot tell, and human-paced input is part of staying unblocked. If a form fill feels slow, that's the trade-off working. -client = AsyncBrowserUse() -browser = await client.browsers.create(proxy_country_code="de") -print(browser.cdp_url) # ws://... -print(browser.live_url) # debug view +{/* TEAM REVIEW: the per-character delay is hardcoded at 5ms in the library source and documented nowhere. Confirm the value, whether cloud uses the same, and whether we want to expose it as a config field (product ticket exists). */} -# With an agent: -# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +## The fast path, all together + +```python +result = await client.run( + "Get the price of iPhone 16 on amazon.de", + use_thinking=False, + use_judge=False, + use_vision=False, +) ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); -const browser = await client.browsers.create({ proxyCountryCode: "de" }); -console.log(browser.cdpUrl); -console.log(browser.liveUrl); +{/* TEAM REVIEW: verify this exact snippet against the current v3/v4 run signature before publish. */} -// With an agent: -// const result = await client.run("Get the price of iPhone 16 on amazon.de", { proxyCountryCode: "de" }); -``` +## Deterministic replay: skip the LLM entirely -## Disable proxies +For workflows you run repeatedly, do the slow run once and replay it: [cache scripts](https://docs.browser-use.com/cloud/agent/cache-script) re-execute a recorded run without LLM calls, which is faster and cheaper than any tuning above. -If your use case does not need proxies, for example QA testing. +## Further reading -```python Python -browser = await client.browsers.create(proxy_country_code=None) +- [Speed matters: how Browser Use achieves the fastest agent execution](https://browser-use.com/posts/speed-matters) +- [The fastest web agent in the world](https://browser-use.com/posts/llm-gateway) +- [What LLM model should I use for Browser Use?](https://browser-use.com/posts/what-model-to-use) — the speed/accuracy tradeoff per model -# With an agent: -# result = await client.run("Go to http://localhost:3000", proxy_country_code=None) -``` -```typescript TypeScript -const browser = await client.browsers.create({ proxyCountryCode: null }); -// With an agent: -// const result = await client.run("Go to http://localhost:3000", { proxyCountryCode: null }); -``` +# Overview +Source: https://docs.browser-use.com/cloud/browser/overview -## Custom proxy -Bring your own proxy server (HTTP or SOCKS5). +A Browser Use cloud browser is a real Chromium instance running on our infrastructure that your code controls remotely over the Chrome DevTools Protocol (CDP). Create one with an API call, get back a `cdpUrl`, and drive it with Playwright, Puppeteer, or any CDP client, the same way you'd drive a local browser. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +The difference from local Chromium is what's built in. Every session runs our [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with anti-fingerprinting patches, [automatic CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha), and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies) in your choice of 195+ countries. None of it needs configuration. -client = AsyncBrowserUse() -browser = await client.browsers.create( -custom_proxy={ - "host": "proxy.example.com", - "port": 8080, - "username": "user", - "password": "pass", -}, -) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +## When to use a cloud browser -const client = new BrowserUse(); -const browser = await client.browsers.create({ - customProxy: { -host: "proxy.example.com", -port: 8080, -username: "user", -password: "pass", - }, -}); -``` +- **Your Playwright/Puppeteer scripts get blocked.** Same code, but running on infrastructure that sites treat as a normal user. +- **You don't want to run browsers.** No Chrome processes, no headless servers, no scaling browser pools. +- **You're building your own agent.** Full CDP access means any framework or custom tooling works. You can also run the [open-source Browser Use agent on a cloud browser](https://docs.browser-use.com/cloud/browser/open-source-agent). +- **You need a watchable, recordable session.** Every session has a [live view](https://docs.browser-use.com/cloud/browser/live-preview) you can open or embed, and optional recording. +If you'd rather describe the task and let AI do the driving, use the [Agent](https://docs.browser-use.com/cloud/agent/overview) instead. The two combine: agents run inside browser sessions, and you can connect your own code to the browser behind an agent run. -# Live preview & recording -Source: https://docs.browser-use.com/cloud/browser/live-preview +## How it fits together +1. [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — SDK, REST, or a single WebSocket URL +2. Connect your framework — [Playwright](https://docs.browser-use.com/cloud/browser/playwright), [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer), or [Selenium](https://docs.browser-use.com/cloud/browser/selenium) +3. Automate as usual — the session behaves like local Chromium with better manners from websites +4. [Manage the session](https://docs.browser-use.com/cloud/browser/sessions) — timeouts, stopping, what you're billed for - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +## Logging into websites + +Sessions start clean by default. To carry login state across sessions, use [profiles / cookie sync](https://docs.browser-use.com/cloud/guides/profile-sync), [authentication](https://docs.browser-use.com/cloud/guides/authentication), and [2FA support](https://docs.browser-use.com/cloud/guides/2fa). + +## Further reading + +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) — how the cloud browser is built +- [Closer to the Metal: Leaving Playwright for CDP](https://browser-use.com/posts/playwright-to-cdp) — why the browser is driven over CDP +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and the [benchmark results](https://browser-use.com/benchmarks) (84.8% BrowserBench, 81% bypass on high-security sites) + + +# Create a browser session +Source: https://docs.browser-use.com/cloud/browser/create -`liveUrl` is returned on session creation. + +Three ways to create a session. All of them return a browser with stealth, CAPTCHA solving, and a residential proxy already on. + +## SDK ```python Python from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +browser = await client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) # connect any CDP client here +print(browser.live_url) # watch the session in a browser tab ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Check how many GitHub stars browser-use has", -}); -console.log(session.liveUrl); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); +console.log(browser.liveUrl); ``` -`liveUrl` is also returned when creating a standalone browser session: +## REST -```python Python -browser = await client.browsers.create() -print(browser.live_url) +```bash +curl -X POST "https://api.browser-use.com/api/v3/browsers" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode": "us", "timeout": 60}' +``` + +## WebSocket URL (no SDK, no create call) + +Connect directly and the session is created for you. Configuration goes in query parameters, and the session stops when the socket disconnects. + +```text +wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us +``` + +## Parameters + +All parameters are optional. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `profileId` | `string` (UUID) | — | Load a saved [profile](https://docs.browser-use.com/cloud/guides/profile-sync) (cookies, localStorage) into the session. | +| `proxyCountryCode` | `string` | `us` | Residential proxy country. Set to `null` to disable the proxy. | +| `timeout` | `int` | `60` | Session lifetime in minutes, 1–240. The session stops automatically when it expires. | +| `browserScreenWidth` | `int` | — | Screen width in pixels, 320–6144. | +| `browserScreenHeight` | `int` | — | Screen height in pixels, 320–3456. | +| `allowResizing` | `bool` | `false` | Allow window resizing during the session. Not recommended: resizing reduces stealth. | +| `customProxy` | `object` | — | Bring your own proxy instead of ours. | +| `enableRecording` | `bool` | `false` | Record the session. The video is available as `recordingUrl` after the session stops. | + +{/* TEAM REVIEW: the WSS connection path previously documented timeout default as 15 minutes; the v3 API spec says 60. Confirm which is correct per method and align the framework pages. */} + +## Response + +`201` with a browser session object: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-15T21:00:00Z", + "startedAt": "2026-07-15T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in REST and TypeScript (`cdpUrl`, `liveUrl`), snake_case in Python (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable, check them before connecting. + +## Errors + +| Status | Meaning | +|--------|---------| +| `403` | Session timeout limit exceeded for your plan. | +| `404` | The `profileId` doesn't exist. | +| `422` | Invalid parameter value. | +| `429` | Too many concurrent active sessions. Stop unused sessions or raise your limit. | + +## Next + +- Connect with [Playwright](https://docs.browser-use.com/cloud/browser/playwright), [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer), or [Selenium](https://docs.browser-use.com/cloud/browser/selenium) +- [Manage the session](https://docs.browser-use.com/cloud/browser/sessions): lifecycle, stopping, billing + + +# Manage browser sessions +Source: https://docs.browser-use.com/cloud/browser/sessions + + +A session has two states: `active` and `stopped`. It leaves `active` in exactly three ways: you stop it, its timeout expires, or (WebSocket connections only) the socket disconnects. + +## Stopping a session + +Stopping is an update, not a delete, and it cannot be undone. + +```python Python +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +await client.browsers.stop(browser.id); +``` +```bash REST +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +There is no `POST /browsers/{id}/stop` endpoint. If you're getting a `404` on a stop call, this is why. + +Stop sessions as soon as you're done with them. Browser time is billed at $0.02/hour until the session stops or times out, whichever comes first. + +```python +browser = await client.browsers.create() +try: + ... # your automation +finally: + await client.browsers.stop(browser.id) +``` + +## Disconnecting vs stopping + +The two connection styles behave differently when your client goes away: + +| | Client disconnects | Session keeps running? | +|---|---|---| +| WebSocket URL (`wss://connect.browser-use.com`) | Socket closes | No — the session stops automatically | +| SDK / REST (`browsers.create()` + CDP) | `pw_browser.close()` only detaches your client | Yes — until you call stop or the timeout expires | + +The SDK behavior is what lets you disconnect and reconnect to the same session, but it also means forgotten sessions keep billing. If you see `429 Too many concurrent active sessions`, list and stop the strays: + +```python +sessions = await client.browsers.list(filter_by="active") +for s in sessions.items: + await client.browsers.stop(s.id) +``` + +## Timeouts + +Every session has a lifetime set at creation: `timeout` in minutes, default `60`, maximum `240` (4 hours). The expiry moment comes back as `timeoutAt` in the session object. A timed-out session stops automatically and cannot be extended or reused, so if a workflow might outlive the default, set the timeout up front: + +```python +browser = await client.browsers.create(timeout=240) +``` + +## Inspecting sessions + +```python +browser = await client.browsers.get(session_id) # one session +sessions = await client.browsers.list(page_size=20) # paginated, filter_by="active" | "stopped" +``` + +The session object carries the operational fields: `status`, `timeoutAt`, `startedAt`, `finishedAt`, live and CDP URLs, plus cost tracking (`browserCost`, `proxyCost`, `proxyUsedMb`). + +## Recordings and downloads + +- Create the session with `enableRecording: true` and `recordingUrl` is populated after the session stops. It is `null` while the session runs and shortly after stopping while the video is processed. +- Files downloaded by the browser during the session are listed at `GET /browsers/{session_id}/downloads`. + +## Related + +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all creation parameters +- [Live preview](https://docs.browser-use.com/cloud/browser/live-preview) — watch or embed a running session + + +# Introduction Stealth +Source: https://docs.browser-use.com/cloud/browser/stealth + + +See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). + +## What's included + +Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. + +- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors. +- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster. +- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services. + +## Residential proxies + +Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration. + +## Further reading + +- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) +- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra) + + +# CAPTCHA Solving +Source: https://docs.browser-use.com/cloud/browser/captcha + + +Browser Use remote browsers are state-of-the-art for stealth. By using a custom Chromium fork with dozens of patches, web agents get blocked by CAPTCHAs and anti-bot systems noticeably less on the websites users care about most. Read how we do it in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark), and see the [benchmark results](https://browser-use.com/benchmarks): 84.8% on BrowserBench and 81% bypass on high-security sites, ahead of every other provider. + +When a CAPTCHA does appear, remote browsers have **automatic CAPTCHA solving** built in. You do not need to configure anything, on the browser or on the attached agent or automation library (Playwright, Puppeteer, Selenium). It is on by default on every plan, including the [free tier](https://docs.browser-use.com/cloud/pricing). + +## Setting up a stealth browser + +There is nothing to turn on. Stealth and CAPTCHA solving come with every session. Start one: + +SDK, REST, or a single WebSocket URL. +Playwright, Puppeteer, or Selenium over CDP. +What the hardened Chromium fork does. +Residential IPs in 195+ countries, on by default. + +## FAQ + +**Does the open-source library solve CAPTCHAs?** + +Without remote browsers, [open-source](https://github.com/browser-use/browser-use) agents have no stealth or CAPTCHA solving. Giving your agent stealth is easy: run it on a remote browser with a single parameter. See [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent). + +**Can I use a third-party CAPTCHA solver?** + +No, we do not support third-party CAPTCHA solver plugins on the browser. If your CAPTCHAs are not being solved properly, reach out and we will look into it. + +**Do I need to enable anything for CAPTCHA solving?** + +No. Remote browsers solve CAPTCHAs for you automatically. + +## Further reading + +- [Prove you are a robot: CAPTCHAs for agents](https://browser-use.com/posts/prove-you-are-a-robot) +- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection) + + +# Proxies +Source: https://docs.browser-use.com/cloud/browser/proxies + + +A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create(proxy_country_code="de") +print(browser.cdp_url) # ws://... +print(browser.live_url) # debug view + +# With an agent: +# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "de" }); +console.log(browser.cdpUrl); +console.log(browser.liveUrl); + +// With an agent: +// const result = await client.run("Get the price of iPhone 16 on amazon.de", { proxyCountryCode: "de" }); +``` + +## Disable proxies + +If your use case does not need proxies, for example QA testing. + +```python Python +browser = await client.browsers.create(proxy_country_code=None) + +# With an agent: +# result = await client.run("Go to http://localhost:3000", proxy_country_code=None) +``` +```typescript TypeScript +const browser = await client.browsers.create({ proxyCountryCode: null }); + +// With an agent: +// const result = await client.run("Go to http://localhost:3000", { proxyCountryCode: null }); +``` + +## Custom proxy + +Bring your own proxy server (HTTP or SOCKS5). + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create( + custom_proxy={ + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + }, +) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create({ + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + }, +}); +``` + +## Further reading + +- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) — how proxy quality affects bypass rates + + +# Screenshots +Source: https://docs.browser-use.com/cloud/browser/screenshots + + +A cloud browser session is a normal CDP endpoint, so screenshots work the way your framework takes them, and they save wherever your code runs. + +## Where screenshots are saved + +The most-asked question first: screenshots taken through Playwright or Puppeteer are written by *your* code, to a path *you* choose. Nothing is stored on the session unless you enable [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads). + +```python Python +from playwright.async_api import async_playwright +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +browser = await client.browsers.create() + +async with async_playwright() as p: + pw = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw.contexts[0].pages[0] + await page.goto("https://example.com") + await page.screenshot(path="shots/example.png") # your machine, your path + +await client.browsers.stop(browser.id) +``` +```typescript TypeScript +import { chromium } from "playwright"; +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const browser = await client.browsers.create(); + +const pw = await chromium.connectOverCDP(browser.cdpUrl); +const page = pw.contexts()[0].pages()[0]; +await page.goto("https://example.com"); +await page.screenshot({ path: "shots/example.png" }); + +await client.browsers.stop(browser.id); +``` + +## Full page, not just the viewport + +By default a screenshot captures the visible viewport. For the whole page, top to bottom: + +```python +await page.screenshot(path="full.png", full_page=True) +``` + +Playwright stitches the scroll automatically. The result contains page content only, no URL bar or browser chrome, because CDP screenshots capture the rendered page, not the window. + +## Resolution + +Screenshot dimensions follow the browser's screen size, set at [session creation](https://docs.browser-use.com/cloud/browser/create) with `browserScreenWidth` and `browserScreenHeight` (320–6144 × 320–3456). Set them explicitly if screenshots must match a target resolution: + +```python +browser = await client.browsers.create(browser_screen_width=1920, browser_screen_height=1080) +``` + +{/* TEAM REVIEW: document the default screen size when width/height are omitted, and whether recording resolution (1920x1080 reported by users) can differ from screenshot resolution — a user reported 1512x770 screenshots vs 1920x1080 recordings. */} + +## Screenshots vs recording + +Screenshots are moments; [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads) is the whole session as video (`enableRecording: true` at create, `recordingUrl` after stop). For debugging agent behavior, recording is usually what you want; for artifacts and QA evidence, screenshots. + +## From agent tasks + +Ask the agent to take screenshots as part of a task and collect them from the run's [workspace files](https://docs.browser-use.com/cloud/agent/workspaces). + +{/* TEAM REVIEW: add the exact API for retrieving agent step screenshots (the v1 /screenshots endpoint users reference) and note whether those images carry element highlight overlays — users ask for unmarked versions. */} + + +# Live preview & recording +Source: https://docs.browser-use.com/cloud/browser/live-preview + + + Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). + +`liveUrl` is returned on session creation. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +session = await client.sessions.create(task="Check how many GitHub stars browser-use has") +print(session.live_url) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); +const session = await client.sessions.create({ + task: "Check how many GitHub stars browser-use has", +}); +console.log(session.liveUrl); +``` + +`liveUrl` is also returned when creating a standalone browser session: + +```python Python +browser = await client.browsers.create() +print(browser.live_url) ``` ```typescript TypeScript const browser = await client.browsers.create(); @@ -1196,14 +1707,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Check how many GitHub stars browser-use has", -enable_recording=True, + "Check how many GitHub stars browser-use has", + enable_recording=True, ) # Waits up to 15s for recording to be ready. Returns [] if no browser was opened. urls = await client.sessions.wait_for_recording(result.id) for url in urls: -print(url) # presigned MP4 download URL + print(url) # presigned MP4 download URL ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1244,29 +1755,32 @@ console.log(stopped.recordingUrl); // presigned MP4 download URL -# Playwright, Puppeteer, Selenium -Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium +# Playwright +Source: https://docs.browser-use.com/cloud/browser/playwright -Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. +Run your Playwright scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Playwright scripts and want to run them on stealth infrastructure +- You need pixel-perfect control (screenshots, specific click coordinates, form filling) +- You want to combine agent tasks with manual browser automation ## Option 1: WebSocket URL (no SDK) Connect with a single URL. All configuration is passed as query parameters. -### Playwright - ```python Python from playwright.async_api import async_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" async with async_playwright() as p: -browser = await p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await browser.close() + browser = await p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -1282,40 +1796,7 @@ await browser.close(); // Browser is automatically stopped when the WebSocket disconnects ``` -### Puppeteer - -```typescript -import puppeteer from "puppeteer-core"; - -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); -const [page] = await browser.pages(); -await page.goto("https://example.com"); -console.log(await page.title()); -await browser.close(); -``` - -### Selenium - -Selenium requires a local WebSocket proxy to connect to Browser Use's remote CDP endpoint. Use [selenium-wire](https://github.com/wkeeling/selenium-wire) or connect through Playwright's CDP bridge instead: - -```python -from playwright.sync_api import sync_playwright - -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: -browser = p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -page.goto("https://example.com") -print(page.title()) -browser.close() -``` - - Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use Playwright or Puppeteer instead. - -## Query parameters +### Query parameters | Parameter | Type | Description | |-----------|------|-------------| @@ -1328,9 +1809,7 @@ browser.close() ## Option 2: SDK -Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright +Create a browser via the SDK, get a `cdp_url`, and connect. The SDK also gives you a `live_url` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview). ```python Python from browser_use_sdk.v3 import AsyncBrowserUse @@ -1342,11 +1821,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com print(browser.live_url) # https://live.browser-use.com?wss=... async with async_playwright() as p: -pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) -page = pw_browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await pw_browser.close() + pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw_browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await pw_browser.close() await client.browsers.stop(browser.id) ``` @@ -1368,7 +1847,98 @@ await pwBrowser.close(); await client.browsers.stop(browser.id); ``` -### Puppeteer +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`) and snake_case in the Python SDK (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. + +### Stopping a session over REST + +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: + +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +## Gotchas + + Use `connect_over_cdp()` / `connectOverCDP()`, **not** `connect()`. Playwright's `connect()` expects a Playwright-protocol server and fails against a CDP endpoint with an opaque `Protocol error (Browser.getVersion)`. + +- **Reuse the existing context.** The session already has a context and page open — use `browser.contexts[0].pages[0]` instead of `browser.new_context()`, so you keep the stealth fingerprint and any loaded [profile](https://docs.browser-use.com/cloud/browser/playwright#query-parameters). +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `pw_browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + +## See also + +- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Puppeteer +Source: https://docs.browser-use.com/cloud/browser/puppeteer + + +Run your Puppeteer scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed. + +When to use this: +- You have existing Puppeteer scripts and want to run them on stealth infrastructure +- You want low-level CDP control from Node.js without managing Chrome yourself +- You want to combine agent tasks with manual browser automation + +## Option 1: WebSocket URL (no SDK) + +Connect with a single URL. All configuration is passed as query parameters. + +```typescript +import puppeteer from "puppeteer-core"; + +const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; + +const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL }); +const [page] = await browser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +await browser.close(); +// Browser is automatically stopped when the WebSocket disconnects +``` + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Option 2: SDK + +Create a browser via the SDK, then resolve the WebSocket endpoint. Unlike Playwright, Puppeteer can't connect to an HTTP CDP URL directly — fetch `/json/version` to get the `webSocketDebuggerUrl` first. ```typescript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1381,17 +1951,186 @@ const browser = await client.browsers.create(); const resp = await fetch(`${browser.cdpUrl}/json/version`); const { webSocketDebuggerUrl } = await resp.json(); -const pwBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); -const [page] = await pwBrowser.pages(); +const pptrBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl }); +const [page] = await pptrBrowser.pages(); await page.goto("https://example.com"); console.log(await page.title()); -await pwBrowser.close(); +await pptrBrowser.close(); await client.browsers.stop(browser.id); ``` +The SDK also gives you a `liveUrl` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview). + +### Create response + +`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with: + +```json +{ + "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e", + "status": "active", + "liveUrl": "https://live.browser-use.com?wss=...", + "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com", + "timeoutAt": "2026-07-14T20:15:00Z", + "startedAt": "2026-07-14T20:00:00Z", + "finishedAt": null, + "proxyUsedMb": "0.0", + "proxyCost": "0.0", + "browserCost": "0.0", + "agentSessionId": null, + "recordingUrl": null +} +``` + +Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`). `cdpUrl` and `liveUrl` are nullable — check them before connecting. + +### Stopping a session over REST + +There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update: + +```bash +curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action": "stop"}' +``` + +## Gotchas + +- **Use `puppeteer-core`.** It's the connect-only package — installing full `puppeteer` downloads a local Chromium you'll never use. +- **`browserWSEndpoint` must be a `ws://`/`wss://` URL.** Passing the SDK's HTTPS `cdpUrl` directly fails; resolve it via `/json/version` as shown above. +- **Viewport.** Puppeteer applies its own 800×600 default viewport after connecting. Pass `defaultViewport: null` to `puppeteer.connect()` to keep the browser's real window size. +- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session. + + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + +## See also + +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Selenium +Source: https://docs.browser-use.com/cloud/browser/selenium + + +Browser Use's cloud browsers speak Chrome DevTools Protocol (CDP) over a remote WebSocket. Selenium can't consume that natively: its `debugger_address` option only supports local `host:port` connections, not remote `wss://` URLs. + +You have two practical paths. + +## Recommended: bridge through a CDP client + +If you're migrating Selenium scripts, connect through Playwright's sync API — the page-automation model (navigate, locate, click, read) maps one-to-one, and you get the [hardened stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) with no configuration. + +```python +from playwright.sync_api import sync_playwright + +WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) + browser.close() +# Browser is automatically stopped when the WebSocket disconnects +``` + +Common Selenium → Playwright equivalents: + +| Selenium | Playwright (sync) | +|---|---| +| `driver.get(url)` | `page.goto(url)` | +| `driver.find_element(By.CSS_SELECTOR, s)` | `page.locator(s)` | +| `element.click()` | `page.locator(s).click()` | +| `element.send_keys(text)` | `page.locator(s).fill(text)` | +| `driver.title` | `page.title()` | +| `WebDriverWait(...).until(...)` | built-in auto-waiting | +| `driver.quit()` | `browser.close()` | + +### Query parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `apiKey` | `string` | **Required.** Your Browser Use API key. | +| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. | +| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). | +| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). | +| `browserScreenWidth` | `int` | Browser width in pixels. | +| `browserScreenHeight` | `int` | Browser height in pixels. | + +## Alternative: keep Selenium with a local proxy + +If you must keep the Selenium API, run a local WebSocket-to-TCP proxy so Chrome's remote debugging endpoint appears as a local `host:port`, e.g. via [selenium-wire](https://github.com/wkeeling/selenium-wire). This adds a moving part we don't manage — for new code, prefer the CDP bridge above. + + Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use [Playwright](https://docs.browser-use.com/cloud/browser/playwright) or [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) instead. + Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. +## See also + +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) connections +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration + + +# Cloud browser + open source agent +Source: https://docs.browser-use.com/cloud/browser/open-source-agent + + +The [open-source library](/open-source/introduction) runs the agent on your machine. By default it also runs the *browser* on your machine, which means no stealth, no residential proxy, and no CAPTCHA solving. This page connects the two: keep your local agent code, point it at a cloud browser. + +## Connect by CDP URL + +Create a cloud browser, then pass its CDP URL to the library's `Browser`: + +```python +import asyncio +from browser_use import Agent, Browser, ChatOpenAI +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse() + cloud_browser = await client.browsers.create(proxy_country_code="us") + + try: + agent = Agent( + task="Find the current price of iPhone 16 on amazon.de", + llm=ChatOpenAI(model="gpt-4o"), + browser=Browser(cdp_url=cloud_browser.cdp_url), + ) + await agent.run() + finally: + await client.browsers.stop(cloud_browser.id) + +asyncio.run(main()) +``` + +The agent behaves exactly as it does locally. The browser it drives is a [stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) with [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies), and you can watch it work through the session's `live_url`. + +{/* TEAM REVIEW: confirm the `use_cloud=True` shorthand on Browser() — parameter name, minimum library version, and whether it should be the primary example instead of the cdp_url form. */} + +## What you get, what you keep + +| | Stays yours | Comes from Cloud | +|---|---|---| +| Agent loop, prompts, custom tools | ✓ | | +| LLM choice and API keys | ✓ | | +| Browser runtime | | ✓ stealth Chromium | +| Proxy / IP | | ✓ residential, 195+ countries | +| CAPTCHA handling | | ✓ automatic | +| Live view and recording | | ✓ per session | + +Billing: only the browser session ($0.02/hour plus proxy data). Your LLM tokens go to your own provider. + +## Related + +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all session parameters +- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud) — the full decision guide +- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions) — always stop sessions when done + # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication @@ -1439,7 +2178,7 @@ profile = await client.profiles.create(name="work-account") # List all response = await client.profiles.list() for p in response.items: -print(p.id, p.name) + print(p.id, p.name) # Search by name response = await client.profiles.list(query="user-id-1") @@ -1484,8 +2223,12 @@ await client.profiles.delete(profileId); Profile state is only saved when the session ends. Always call `sessions.stop()` when you are done — if a session is left open or times out, changes may not be persisted. Every code path that uses a profile must stop the session, including error handlers. +## Further reading + +- [How to authenticate AI web agents](https://browser-use.com/posts/web-agent-authentication) -# Sync local and cloud cookies + +# Profiles / Cookie sync Source: https://docs.browser-use.com/cloud/guides/profile-sync @@ -1588,190 +2331,949 @@ await client.sessions.stop(newSession.id); Cookies expire. Some sites stay logged in for months, others expire daily. If your sessions start hitting login pages again, re-authenticate and save the profile. - Always call `sessions.stop()` after you're done — profile state is only saved when the session ends cleanly. + Always call `sessions.stop()` after you're done — profile state is only saved when the session ends cleanly. + +--- + +## 2. Human in the loop + +Let the agent navigate to the login page, then a human takes over to complete 2FA via the live browser view. The agent continues after. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() +session = await client.sessions.create() +print(f"Live view: {session.live_url}") + +# Agent navigates to login +result = await client.run( + "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", + session_id=session.id, +) + +# Human completes 2FA in the live view +input("Complete 2FA in the live view, then press Enter...") + +# Agent continues +result = await client.run( + "You are now logged in. Go to the dashboard and export the monthly report", + session_id=session.id, +) +print(result.output) +await client.sessions.stop(session.id) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; +import * as readline from "readline"; + +const client = new BrowserUse(); +const session = await client.sessions.create(); +console.log(`Live view: ${session.liveUrl}`); + +// Agent navigates to login +await client.run( + "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", + { sessionId: session.id }, +); + +// Human completes 2FA in the live view +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); +await new Promise((resolve) => rl.question("Complete 2FA in the live view, then press Enter...", resolve)); +rl.close(); + +// Agent continues +const result = await client.run( + "You are now logged in. Go to the dashboard and export the monthly report", + { sessionId: session.id }, +); +console.log(result.output); +await client.sessions.stop(session.id); +``` + +See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. + +--- + +## 3. Agent Mail + +When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. + +Agent Mail is **enabled by default** (`agentmail=True`). Each session gets a unique email address (`session.agentmail_email`). The agent can send and receive emails during the task. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() + +result = await client.run( + """ + 1. Go to example.com/signup + 2. Sign up with the agent's email address (use the email available to you) + 3. Check your email inbox for the verification code + 4. Enter the code on the website + 5. Complete the registration + """, + agentmail=True, # default, shown for clarity +) +print(result.output) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); + +const result = await client.run( + `1. Go to example.com/signup + 2. Sign up with the agent's email address (use the email available to you) + 3. Check your email inbox for the verification code + 4. Enter the code on the website + 5. Complete the registration`, + { agentmail: true }, // default, shown for clarity +); +console.log(result.output); +``` + +### For end-client automation + +If you're automating on behalf of your users and they need to receive 2FA codes: + +1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). +2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. + +This way, your client's real email stays private — the agent only sees the forwarded verification emails. + +### Connect external email via Composio + +You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. + +--- + +## 4. TOTP secret in prompt + +If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. + +When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. + +```python Python +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse() + +# The TOTP secret from your authenticator setup — NOT the 6-digit code +totp_secret = "JBSWY3DPEHPK3PXP" + +result = await client.run( + f""" + Log into example.com with username user@example.com and password mypassword. + When prompted for a 2FA code, generate one using pyotp: + + import pyotp + totp = pyotp.TOTP("{totp_secret}") + code = totp.now() + + Enter the generated code. + """, +) +print(result.output) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse(); + +// The TOTP secret from your authenticator setup — NOT the 6-digit code +const totpSecret = "JBSWY3DPEHPK3PXP"; + +const result = await client.run( + `Log into example.com with username user@example.com and password mypassword. + When prompted for a 2FA code, generate one using pyotp: + + import pyotp + totp = pyotp.TOTP("${totpSecret}") + code = totp.now() + + Enter the generated code.`, +); +console.log(result.output); +``` + +This works because the Browser Use agent can execute Python code as part of its task. The agent runs `pyotp.TOTP(secret).now()` to generate a time-based 6-digit code, then types it into the 2FA field. + +### Where to find TOTP secrets + +- **1Password**: Edit item → One-Time Password → Show secret +- **Google Authenticator**: During setup, click "Can't scan it?" to see the key +- **Authy**: Export via desktop app settings +- **Most sites**: Look for "manual entry" or "setup key" during 2FA enrollment + +--- + +## Which approach should I use? + +Start with **Profiles** — log in once, reuse cookies. If cookies expire frequently, add **TOTP secret in prompt** for fully automated re-login. +Use **Profiles** with one profile per user. For initial login, use **Human in the loop** — your user logs in once via the live view, then the agent reuses the session. For email 2FA, set up **Agent Mail** with email forwarding from your user. +Use **Agent Mail** (enabled by default). For end-client scenarios, have them forward 2FA emails to a dedicated inbox. +Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. + + +# MCP Server +Source: https://docs.browser-use.com/cloud/guides/mcp-server + + +``` +https://api.browser-use.com/v3/mcp +``` + +Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). + +## Claude Code + +```bash +claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp +``` + +## Claude Desktop + +Add to `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Cursor + +Add to `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Windsurf + +Add to `~/.codeium/windsurf/mcp_config.json`: + +```json +{ + "mcpServers": { + "browser-use": { + "serverUrl": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } + } +} +``` + +## Available Tools + +| Tool | Description | +|------|-------------| +| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | +| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | +| `send_task` | Send a follow-up task to an idle keep-alive session. | +| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | +| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | +| `list_sessions` | List recent sessions with status and cost. | +| `list_browser_profiles` | List browser profiles for authenticated tasks. | + + +# Webhooks +Source: https://docs.browser-use.com/cloud/guides/webhooks + + +Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). + +## Events + +| Event | When | +|-------|------| +| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | +| `test` | Webhook test ping | + +## Payload + +```json +{ + "type": "agent.task.status_update", + "timestamp": "2025-01-15T10:30:00Z", + "payload": { + "task_id": "task_abc123", + "session_id": "session_xyz", + "status": "idle", + "metadata": {} + } +} +``` + +## Signature verification + +Every webhook request includes two headers: + +- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload +- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent + +The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. + +```python Python +import hashlib +import hmac +import json +import time + +def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + return False + if abs(time.time() - ts) > 300: + return False + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() + return hmac.compare_digest(expected, signature) +``` +```typescript TypeScript +import { createHmac, timingSafeEqual } from "crypto"; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { + // Reject requests older than 5 minutes + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", secret).update(message).digest("hex"); + return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); +} +``` + +## Example: Express webhook handler + +```typescript +import express from "express"; +import { createHmac, timingSafeEqual } from "crypto"; + +const app = express(); +app.use(express.raw({ type: "application/json" })); + +const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +app.post("/webhook", (req, res) => { + const signature = req.headers["x-browser-use-signature"] as string; + const timestamp = req.headers["x-browser-use-timestamp"] as string; + + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { + return res.status(401).send("Request too old"); + } + + const body = req.body.toString(); + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); + + if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { + return res.status(401).send("Invalid signature"); + } + + if (payload.type === "agent.task.status_update") { + const { task_id, status, session_id } = payload.payload; + console.log(`Task ${task_id} is now ${status}`); + } + + res.status(200).send("OK"); +}); + +app.listen(3000); +``` + +## Example: FastAPI webhook handler + +```python +from fastapi import FastAPI, Request, HTTPException +import hashlib +import hmac +import json +import os +import time + +app = FastAPI() + +WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] + +@app.post("/webhook") +async def handle_webhook(request: Request): + body = await request.body() + signature = request.headers.get("x-browser-use-signature", "") + timestamp = request.headers.get("x-browser-use-timestamp", "") + + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + raise HTTPException(status_code=401, detail="Invalid timestamp") + if abs(time.time() - ts) > 300: + raise HTTPException(status_code=401, detail="Request too old") + + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() + + if not hmac.compare_digest(expected, signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + if payload["type"] == "agent.task.status_update": + task_id = payload["payload"]["task_id"] + status = payload["payload"]["status"] + print(f"Task {task_id} is now {status}") + + return {"status": "ok"} +``` + + For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. + + +# x402 (pay-per-request) +Source: https://docs.browser-use.com/cloud/guides/x402 + + +{/* prettier-ignore-start */} + +[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. + +x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. + + +**New to crypto?** Here's the gist: + +- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. +- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. +- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. +- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. + + +**Three ways to start, ranked by laziness:** + +One command. Claude does the wallet setup, funding walkthrough, and +verification for you. +One line in your Python or TypeScript app. Bring your own wallet. +Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. + +## Claude Code quickstart + +The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: + +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` + +Then in Claude Code: + +``` +> /x402 +``` + +Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. + + Already have a Browser Use Cloud account? The skill detects this and switches + to **top-up mode**, adding credits to that existing account instead of + creating a new, wallet-keyed one. + +## SDK quickstart + +The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. + +```bash Python +pip install "browser-use-sdk[x402]" +``` +```bash TypeScript +npm install browser-use-sdk @x402/fetch @x402/evm viem +``` + +```python Python +import asyncio +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base + result = await client.run("Go to example.com and tell me the heading.") + print(result.output) + +asyncio.run(main()) +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base +const result = await client.run("Go to example.com and tell me the heading."); +console.log(result.output); +``` + +Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: + +```python Python +client = AsyncBrowserUse() # auto-detects from env +``` +```typescript TypeScript +const client = new BrowserUse(); // auto-detects from env +``` + + Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. + +## Raw HTTP quickstart + +Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): + +```python +import asyncio + +from x402 import x402Client +from x402.http.clients import x402HttpxClient +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account + +async def main(): + client = x402Client() + register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) + + async with x402HttpxClient(client, timeout=120.0) as http: + response = await http.post( + "https://x402.api.browser-use.com/api/v3/sessions", + json={"task": "..."}, + ) + print(response.status_code, response.text[:500]) + +asyncio.run(main()) +``` + +`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. + +## What you need + +- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app +- **USD Coin (USDC) on Base mainnet** +- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) + +You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. + + +## Pricing and credits + +Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** + + **Mid-task drain still terminates the task.** Browser Use sessions run on a + worker that doesn't see x402, so once a long-running task starts and burns + through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause + and wait for the next x402 payment. The `$5` default exists so most tasks + complete without hitting this; for expensive models (e.g. Opus) or long + sessions, pre-fund with multiple requests before kicking off the task. + +See the [pricing page](https://browser-use.com/pricing) for model and browser costs. + +## Topping up an existing account + +If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: + +```python Python +import asyncio + +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse( + api_key="bu_...", # existing API key getting topped up + x402_private_key="0x...", # wallet that pays + base_url="https://x402.api.browser-use.com/api/v3", +) +async def main(): + result = await client.run("...") # $5 USDC charged, credited to the API key's project + print(result.output) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ + apiKey: "bu_...", + x402PrivateKey: "0x...", + baseUrl: "https://x402.api.browser-use.com/api/v3", +}); +const result = await client.run("..."); +``` + +When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: + +- Agents that ran out of free-tier credits and need to keep going +- Adding credits via crypto when you already have a regular Browser Use account +- Multi-wallet setups funding one shared account + +## Checking your credit balance + +When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. + +This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. + +To check how much credit that account has left, use the method below: + +```python Python +import asyncio + +from browser_use_sdk.v3 import get_wallet_balance + +async def main(): + balance = await get_wallet_balance("0x...") # same wallet private key you pay with + print(balance["total_credits_usd"]) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { getWalletBalance } from "browser-use-sdk/v3"; + +const balance = await getWalletBalance("0x..."); // same wallet private key you pay with +console.log(balance.total_credits_usd); +``` + +The response contains: + +| Field | Description | +| ------------------------ | ------------------------------------------------------------------------------- | +| `wallet` | The wallet address (lowercased) | +| `project_id` | The account (project) tied to your wallet that the credits live in | +| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | +| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | + + This is for accounts created from a wallet (the default x402 mode). If you're + [topping up an existing account](#topping-up-an-existing-account), check that + account's balance the normal way with your API key via + `client.billing.account()`. A wallet that has never paid yet has no account, + so the call returns `404` until the first payment. + + The SDK signs a fixed, server-defined message + ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with + Ethereum" mechanism) with your wallet's private key. The signature proves you + control the address without moving any funds. The server recovers the signer, + matches it to the wallet's project, and returns the balance. + +## How it works + +Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. + +A bit more detail: + +1. Your code makes a request (e.g. "run this task"). +2. The SDK auto-signs the payment from your wallet and resends the request. +3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. +4. We run your task and send back the result. + +## Wallet setup + +If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. + +Get the [MetaMask browser extension](https://metamask.io) via the official +site only. Create a new wallet, save the seed phrase somewhere offline, set +a password. +By default, most wallets only show Ethereum. You need to add **Base** (the +network we accept payments on) so your wallet can hold USDC there. +Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and +pay with credit card, bank, etc. The USDC lands directly in your wallet. +In MetaMask: click the account menu → **Account details** → **Private keys** +→ enter your password → copy. That string (starts with `0x`) is your +`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in +their account settings. + + Wallets hold real money, and anyone with the private key can drain it. Be + careful with your keys. + +## Advanced: bring your own x402 client + +For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: + +```python Python +from x402 import x402Client +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account +from browser_use_sdk.v3 import AsyncBrowserUse + +x402 = x402Client() +register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) +client = AsyncBrowserUse(x402=x402) + +``` + +```typescript TypeScript +import { x402Client } from "@x402/fetch"; +import { ExactEvmScheme } from "@x402/evm"; +import { privateKeyToAccount } from "viem/accounts"; +import { BrowserUse } from "browser-use-sdk/v3"; + +const x402 = new x402Client(); +x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); +const client = new BrowserUse({ x402 }); +``` + +## Troubleshooting + +Two likely causes: + +- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. +- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). + + + You haven't installed the optional x402 deps. Run `pip install + "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` + (TypeScript). + + We verified your payment request but couldn't credit your project, so we + deliberately did not settle on-chain. No USDC was moved, so just retry. This + is rare. + + Wait a few seconds. Settlement and credit grant happen in the same request, + but the response may be sent before the credit grant fully commits. If credits + still show `$0` after a few minutes, contact support with your wallet address. + (Conversely, if a payment settles but the request itself then fails, we + automatically reclaim the credits so you aren't charged for nothing.) + +`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. + +## Related + +- [x402 protocol spec](https://www.x402.org) +- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use +- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) + +{/* prettier-ignore-end */} + ---- +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code -## 2. Human in the loop -Let the agent navigate to the login page, then a human takes over to complete 2FA via the live browser view. The agent continues after. +[Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) is Anthropic's agentic coding tool that runs in the terminal. Add Browser Use and it gets full cloud browser automation — anti-detect profiles, CAPTCHA solving, residential proxies in 195+ countries, persistent profiles, and stealth browsing. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Setup -client = AsyncBrowserUse() -session = await client.sessions.create() -print(f"Live view: {session.live_url}") +**1. Install the CLI** -# Agent navigates to login -result = await client.run( -"Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", -session_id=session.id, -) +```bash +uv tool install browser-use +``` -# Human completes 2FA in the live view -input("Complete 2FA in the live view, then press Enter...") +**2. Verify the installation** -# Agent continues -result = await client.run( -"You are now logged in. Go to the dashboard and export the monthly report", -session_id=session.id, -) -print(result.output) -await client.sessions.stop(session.id) +```bash +browser-use doctor ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; -const client = new BrowserUse(); -const session = await client.sessions.create(); -console.log(`Live view: ${session.liveUrl}`); +**3. Register the skill** -// Agent navigates to login -await client.run( - "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA", - { sessionId: session.id }, -); +Register the Browser Use skill with the installed CLI: -// Human completes 2FA in the live view -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => rl.question("Complete 2FA in the live view, then press Enter...", resolve)); -rl.close(); +```bash +browser-use skill install +``` -// Agent continues -const result = await client.run( - "You are now logged in. Go to the dashboard and export the monthly report", - { sessionId: session.id }, -); -console.log(result.output); -await client.sessions.stop(session.id); +**4. Authenticate for cloud browsers** + +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. ---- +**5. Use it** -## 3. Agent Mail +Claude Code uses its bash tool to run CLI commands directly: -When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` -Agent Mail is **enabled by default** (`agentmail=True`). Each session gets a unique email address (`session.agentmail_email`). The agent can send and receive emails during the task. +For the complete CLI reference and Python execution examples, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Agent Self-Registration -client = AsyncBrowserUse() +Claude Code can provision its own Browser Use API key autonomously — no human interaction needed. The free tier includes unlimited browser hours, free proxies in 195+ countries, persistent browser profiles, CAPTCHA solving, and stealth browsing at zero cost. -result = await client.run( -""" -1. Go to example.com/signup -2. Sign up with the agent's email address (use the email available to you) -3. Check your email inbox for the verification code -4. Enter the code on the website -5. Complete the registration -""", -agentmail=True, # default, shown for clarity -) -print(result.output) +Install the Browser Use CLI and skill: + +```bash +uv tool install browser-use +browser-use skill install ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); +Claude Code can follow the [REST flow](https://docs.browser-use.com/cloud/agent-signup#rest-flow) to request and verify a challenge, then put the returned key in its shell environment: -const result = await client.run( - `1. Go to example.com/signup - 2. Sign up with the agent's email address (use the email available to you) - 3. Check your email inbox for the verification code - 4. Enter the code on the website - 5. Complete the registration`, - { agentmail: true }, // default, shown for clarity -); -console.log(result.output); +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` -### For end-client automation +### Claim the account (optional) -If you're automating on behalf of your users and they need to receive 2FA codes: +If the human wants to see the account in the dashboard later, use the [claim endpoint](https://docs.browser-use.com/cloud/agent-signup#claim-the-account). The returned claim URL is valid for 1 hour. -1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). -2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. +## Pay with USDC via x402 -This way, your client's real email stays private — the agent only sees the forwarded verification emails. +Two ways to use x402 with Browser Use Cloud: -### Connect external email via Composio +- **Top up an existing account** — add credits to your API key (e.g. one created via Agent Self-Registration above) using USDC. No credit card required. Use this when free credits run out. +- **Accountless** — wallet IS the identity, no signup needed. Pure x402 / agent-economy native. Use this for autonomous agents that hold their own wallet. -You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. +Install the skill: ---- +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` -## 4. TOTP secret in prompt +Then in Claude Code: -If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. +``` +> /x402 +``` -When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. +The skill asks whether you have an existing API key (top-up mode) or want accountless mode, then walks you through generating (or importing) an EVM wallet, funding it via Coinbase, and running a verification task. You'll need ~$5 of USDC on Base mainnet. Each top-up is $1. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +For the SDK API and protocol details, see the [x402 guide](https://docs.browser-use.com/cloud/guides/x402). -client = AsyncBrowserUse() -# The TOTP secret from your authenticator setup — NOT the 6-digit code -totp_secret = "JBSWY3DPEHPK3PXP" +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents -result = await client.run( -f""" -Log into example.com with username user@example.com and password mypassword. -When prompted for a 2FA code, generate one using pyotp: -import pyotp -totp = pyotp.TOTP("{totp_secret}") -code = totp.now() +[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents) run on Anthropic's hosted platform. Install the `browser-use` CLI in the agent's environment and it can drive a stealth cloud browser — with proxies, CAPTCHA solving, live view, and recording. Your API key stays in a credential vault; the model never sees it. -Enter the generated code. -""", -) -print(result.output) +The sandbox can't run a local browser, so the agent starts a named Browser Use Cloud browser and drives it with `browser-use <<'PY'` Python snippets. + +## 1. Create an environment + +Pre-install the CLI so it's ready at session start (no runtime install). + +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -const client = new BrowserUse(); +## 2. Create a credential vault -// The TOTP secret from your authenticator setup — NOT the 6-digit code -const totpSecret = "JBSWY3DPEHPK3PXP"; +Store your key as an environment variable so the CLI reads it and the model never does. Get one at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -const result = await client.run( - `Log into example.com with username user@example.com and password mypassword. - When prompted for a 2FA code, generate one using pyotp: +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | - import pyotp - totp = pyotp.TOTP("${totpSecret}") - code = totp.now() +## 3. Create the agent - Enter the generated code.`, -); -console.log(result.output); +Tell it to use the CLI in cloud mode. + +```yaml +name: browser agent +model: + id: claude-opus-4-8 +description: Drives a stealth cloud browser with the Browser Use CLI. +system: | + You are a browser agent. Use the `browser-use` CLI to complete web tasks. + Never launch a local browser in this sandbox. Start a named cloud browser: + browser-use <<'PY' + start_remote_daemon("managed") + PY + Then run browser work through the same name: + BU_NAME=managed browser-use <<'PY' + new_tab("https://example.com") + print(page_info()) + PY + Your BROWSER_USE_API_KEY is in the environment; never print it. +tools: + - type: agent_toolset_20260401 # shell access so the agent can run the CLI + default_config: + enabled: true + permission_policy: + type: always_allow ``` -This works because the Browser Use agent can execute Python code as part of its task. The agent runs `pyotp.TOTP(secret).now()` to generate a time-based 6-digit code, then types it into the 2FA field. +## 4. Start a session and send a task -### Where to find TOTP secrets +The Console only observes; kick the agent off with a `user.message` event. -- **1Password**: Edit item → One-Time Password → Show secret -- **Google Authenticator**: During setup, click "Can't scan it?" to see the key -- **Authy**: Export via desktop app settings -- **Most sites**: Look for "manual entry" or "setup key" during 2FA enrollment +```bash +curl -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: managed-agents-2026-04-01" \ + -H "content-type: application/json" \ + -d '{"events":[{"type":"user.message","content":[{"type":"text", + "text":"Get the top 5 Hacker News stories with their links."}]}]}' +``` ---- +## 5. Watch it run -## Which approach should I use? +The agent starts a named cloud browser, runs Python helper snippets through `browser-use`, then returns the result. The session shows up in [cloud.browser-use.com](https://cloud.browser-use.com) → **Remote Browsers** with a **Live View** and an **mp4 recording**. -Start with **Profiles** — log in once, reuse cookies. If cookies expire frequently, add **TOTP secret in prompt** for fully automated re-login. -Use **Profiles** with one profile per user. For initial login, use **Human in the loop** — your user logs in once via the live view, then the agent reuses the session. For email 2FA, set up **Agent Mail** with email forwarding from your user. -Use **Agent Mail** (enabled by default). For end-client scenarios, have them forward 2FA emails to a dedicated inbox. -Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. + Always use a cloud browser — the Managed Agents sandbox has no GUI, so a local + browser won't start. Cloud mode also gives you stealth, residential proxies, + live view, and recording. # OpenClaw @@ -1801,16 +3303,16 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: ```json5 { browser: { -enabled: true, -defaultProfile: "browser-use", -remoteCdpTimeoutMs: 3000, -remoteCdpHandshakeTimeoutMs: 5000, -profiles: { - "browser-use": { - cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", - color: "#ff750e", - }, -}, + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, }, } ``` @@ -1848,7 +3350,7 @@ The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser a **1. Install the CLI** ```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash +uv tool install browser-use ``` **2. Verify the installation** @@ -1865,269 +3367,141 @@ Paste this setup prompt into your OpenClaw agent: Install or upgrade browser-use with `uv tool install --python 3.12 --upgrade --force 'browser-use @ git+https://github.com/browser-use/browser-use.git'`, run `browser-use skill install`, and connect it to my browser. Follow https://github.com/browser-use/browser-use if setup or connection fails. ``` -Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to navigate pages, click elements, fill forms, take screenshots, extract data, and more. The skill file teaches the agent the full command set. - -For the complete CLI reference and advanced features like cloud browsers, tunnels, sessions, and Python execution, see the [README](https://github.com/browser-use/browser-use/blob/main/browser_use/skill_cli/README.md) and the [Browser Use docs](https://docs.browser-use.com). +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. - -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -``` -https://api.browser-use.com/v3/mcp -``` +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -## Claude Code +[Hermes Agent](https://github.com/nousresearch/hermes-agent) is an open-source, self-improving AI agent by Nous Research. It has built-in browser automation tools that work with local Chromium out of the box. Add Browser Use and those tools run on cloud browsers with anti-detect profiles, residential proxies in 195+ countries, and stealth browsing. -```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp -``` +Two ways to set it up: configure Browser Use as Hermes's cloud browser backend, or install the Browser Use CLI and let Hermes drive it directly. -## Claude Desktop +## Option 1: Cloud Browser Backend -Add to `claude_desktop_config.json`: +Hermes has built-in browser tools (`browser_navigate`, `browser_click`, `browser_snapshot`, etc.) that default to local Chromium. Point them at Browser Use cloud browsers instead — no extra dependencies, same Hermes experience. -```json -{ - "mcpServers": { -"browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} -``` +### Setup -## Cursor +**1. Get your API key** -Add to `.cursor/mcp.json`: +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com) and copy your API key from [Settings → API Keys](https://cloud.browser-use.com/settings?tab=api-keys&new=1). -```json -{ - "mcpServers": { -"browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} -``` +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -## Windsurf +**2. Configure Hermes** -Add to `~/.codeium/windsurf/mcp_config.json`: +Run the setup wizard: -```json -{ - "mcpServers": { -"browser-use": { - "serverUrl": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } -} - } -} +```bash +hermes setup tools ``` -## Available Tools - -| Tool | Description | -|------|-------------| -| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | -| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | -| `send_task` | Send a follow-up task to an idle keep-alive session. | -| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | -| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | -| `list_sessions` | List recent sessions with status and cost. | -| `list_browser_profiles` | List browser profiles for authenticated tasks. | - +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -# Webhooks -Source: https://docs.browser-use.com/cloud/guides/webhooks +Or configure manually — add your key to `~/.hermes/.env`: +```bash +BROWSER_USE_API_KEY=your_key_here +``` -Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). +And set the provider in `~/.hermes/config.yaml`: -## Events +```yaml +browser: + cloud_provider: browser-use +``` -| Event | When | -|-------|------| -| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | -| `test` | Webhook test ping | +**3. Use it** -## Payload +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: -```json -{ - "type": "agent.task.status_update", - "timestamp": "2025-01-15T10:30:00Z", - "payload": { -"task_id": "task_abc123", -"session_id": "session_xyz", -"status": "idle", -"metadata": {} - } -} +``` +> Find the top trending repositories on GitHub today and summarize them ``` -## Signature verification - -Every webhook request includes two headers: +## Option 2: Browser Use CLI -- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload -- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent +The [Browser Use CLI](https://docs.browser-use.com/open-source/browser-use-cli) is a standalone tool that gives Hermes browser automation through terminal commands. Hermes drives the browser directly via its terminal tool, using Browser Harness and Python helpers through the `browser-use` command. -The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. +### Setup -```python Python -import hashlib -import hmac -import json -import time +**1. Install the CLI** -def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: -# Reject requests older than 5 minutes -try: - ts = int(timestamp) -except (ValueError, TypeError): - return False -if abs(time.time() - ts) > 300: - return False -payload = json.loads(body) -message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" -expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() -return hmac.compare_digest(expected, signature) +```bash +uv tool install browser-use ``` -```typescript TypeScript -import { createHmac, timingSafeEqual } from "crypto"; -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { -return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} +**2. Verify the installation** -function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { - // Reject requests older than 5 minutes - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", secret).update(message).digest("hex"); - return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); -} +```bash +browser-use doctor ``` -## Example: Express webhook handler - -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; +**3. Register the skill** -const app = express(); -app.use(express.raw({ type: "application/json" })); +Register the Browser Use skill with the installed CLI: -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; +```bash +browser-use skill install +``` -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { -return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} +Or ask Hermes directly in chat to install it. -app.post("/webhook", (req, res) => { - const signature = req.headers["x-browser-use-signature"] as string; - const timestamp = req.headers["x-browser-use-timestamp"] as string; +**4. Authenticate for cloud browsers** - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { -return res.status(401).send("Request too old"); - } +Authenticate with your API key: - const body = req.body.toString(); - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); +```bash +browser-use auth login +``` - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { -return res.status(401).send("Invalid signature"); - } +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - if (payload.type === "agent.task.status_update") { -const { task_id, status, session_id } = payload.payload; -console.log(`Task ${task_id} is now ${status}`); - } +**5. Use it** - res.status(200).send("OK"); -}); +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: -app.listen(3000); +``` +> Use browser-use to open github.com/trending and summarize the top repos ``` -## Example: FastAPI webhook handler +For the complete CLI reference and Python execution examples, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time +## Agent Self-Registration -app = FastAPI() +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. -WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] +Install the Browser Use CLI and skill: -@app.post("/webhook") -async def handle_webhook(request: Request): -body = await request.body() -signature = request.headers.get("x-browser-use-signature", "") -timestamp = request.headers.get("x-browser-use-timestamp", "") +```bash +uv tool install browser-use +browser-use skill install +``` -# Reject requests older than 5 minutes -try: - ts = int(timestamp) -except (ValueError, TypeError): - raise HTTPException(status_code=401, detail="Invalid timestamp") -if abs(time.time() - ts) > 300: - raise HTTPException(status_code=401, detail="Request too old") +The agent can follow the [REST flow](https://docs.browser-use.com/cloud/agent-signup#rest-flow) to request and verify a challenge, then use the returned API key. + +**Copy the key to Hermes config** -payload = json.loads(body) -message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" -expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() +For the cloud browser backend (Option 1): -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +```bash +hermes config set BROWSER_USE_API_KEY +``` -if payload["type"] == "agent.task.status_update": - task_id = payload["payload"]["task_id"] - status = payload["payload"]["status"] - print(f"Task {task_id} is now {status}") +For CLI mode (Option 2), put the key in the agent's shell environment: -return {"status": "ok"} +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` - For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. +### Claim the account (optional) + +If the human wants to see the account in the dashboard later, use the [claim endpoint](https://docs.browser-use.com/cloud/agent-signup#claim-the-account). The returned claim URL is valid for 1 hour. # n8n @@ -2225,8 +3599,8 @@ import { client } from "./api"; export async function createSession() { const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, + keepAlive: true, + enableRecording: true, }); return { id: session.id, liveUrl: session.liveUrl, status: session.status }; } @@ -2243,7 +3617,7 @@ async function handleSend(message: string) { const session = await createSession(); router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` + `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` ); } ``` @@ -2259,7 +3633,7 @@ const streamTask = useCallback(async (task: string) => { const run = client.run(task, { sessionId }); for await (const msg of run) { -setMessages((prev) => [...prev, msg]); + setMessages((prev) => [...prev, msg]); } // Iterator done — task reached terminal state @@ -2303,7 +3677,7 @@ useEffect(() => { if (!isTerminal) return; client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); + if (urls.length) setRecordingUrls(urls); }); }, [isTerminal, sessionId]); ``` @@ -2331,23 +3705,23 @@ The session page consumes everything through a context provider: ```typescript session/[id]/page.tsx function SessionPage() { const { session, turns, isBusy, isTerminal, recordingUrls, sendMessage, stopTask } = -useSession(); + useSession(); return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
+
+ {/* Chat column */} +
+ + +
+ + {/* Live browser view — liveUrl available from session creation */} + +
); } ``` @@ -2364,6 +3738,116 @@ useSession(); | `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup + + +An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. + +The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. + +## REST flow + +### 1. Request a challenge + +```bash +curl -X POST https://api.browser-use.com/cloud/signup \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +Request body, optional (include a user email/name if available): + +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` + +Response: + +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} +``` + +### 2. Solve the challenge + +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. + +### 3. Verify the answer + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/verify \ + -H "Content-Type: application/json" \ + -d '{"challenge_id":"uuid","answer":"144.00"}' +``` + +Request body: + +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} +``` + +Response: + +```json +{ + "api_key": "bu_..." +} +``` + +Use the returned key for Browser Use Cloud API requests. + +For example, create a browser session: + +```bash +curl -X POST https://api.browser-use.com/api/v3/browsers \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + +## Claim the account + +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." +``` + +Response: + +```json +{ + "claim_url": "https://..." +} +``` + +The claim URL is valid for 1 hour. + +## CLI usage + +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: + +```bash +uv tool install browser-use +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +Replace `bu_...` with the key returned by the REST flow. + + # Grow Therapy provider search Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare @@ -2400,28 +3884,28 @@ const client = new BrowserUse(); ```python Python class Provider(BaseModel): -name: str -title: str -specialties: list[str] -insurance_plans: list[str] -rating: float | None = None -next_available: str | None = None + name: str + title: str + specialties: list[str] + insurance_plans: list[str] + rating: float | None = None + next_available: str | None = None class ProviderSearch(BaseModel): -providers: list[Provider] -total_found: int | None = None -location: str -specialty: str + providers: list[Provider] + total_found: int | None = None + location: str + specialty: str ``` ```typescript TypeScript const ProviderSearch = z.object({ providers: z.array(z.object({ -name: z.string(), -title: z.string(), -specialties: z.array(z.string()), -insurancePlans: z.array(z.string()), -rating: z.number().nullable(), -nextAvailable: z.string().nullable(), + name: z.string(), + title: z.string(), + specialties: z.array(z.string()), + insurancePlans: z.array(z.string()), + rating: z.number().nullable(), + nextAvailable: z.string().nullable(), })), totalFound: z.number().nullable(), location: z.string(), @@ -2442,19 +3926,19 @@ const workspace = await client.workspaces.create({ name: "grow-therapy-search" } ```python Python result = await client.run( -"Go to growtherapy.com and search for therapists in {{New York}} " -"who specialize in {{anxiety}} and accept insurance. " -"Return the first 5 provider profiles as JSON.", -workspace_id=str(workspace.id), -output_schema=ProviderSearch, + "Go to growtherapy.com and search for therapists in {{New York}} " + "who specialize in {{anxiety}} and accept insurance. " + "Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, ) for p in result.output.providers: -print(f"{p.name} ({p.title})") -print(f" Specialties: {', '.join(p.specialties)}") -print(f" Rating: {p.rating}") -print(f" Next available: {p.next_available}") -print() + print(f"{p.name} ({p.title})") + print(f" Specialties: {', '.join(p.specialties)}") + print(f" Rating: {p.rating}") + print(f" Next available: {p.next_available}") + print() ``` ```typescript TypeScript const result = await client.run( @@ -2481,16 +3965,16 @@ locations = ["Los Angeles", "Chicago", "Houston", "Miami"] specialties = ["depression", "trauma", "ADHD"] for location in locations: -for specialty in specialties: - result = await client.run( - f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " - f"who specialize in {{{{{specialty}}}}} and accept insurance. " - f"Return the first 5 provider profiles as JSON.", - workspace_id=str(workspace.id), - output_schema=ProviderSearch, - ) - count = len(result.output.providers) - print(f"{location} / {specialty}: {count} providers found") + for specialty in specialties: + result = await client.run( + f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " + f"who specialize in {{{{{specialty}}}}} and accept insurance. " + f"Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, + ) + count = len(result.output.providers) + print(f"{location} / {specialty}: {count} providers found") ``` ```typescript TypeScript const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; @@ -2498,13 +3982,13 @@ const specialties = ["depression", "trauma", "ADHD"]; for (const location of locations) { for (const specialty of specialties) { -const result = await client.run( - `Go to growtherapy.com and search for therapists in {{${location}}} ` + - `who specialize in {{${specialty}}} and accept insurance. ` + - `Return the first 5 provider profiles as JSON.`, - { workspaceId: workspace.id, schema: ProviderSearch }, -); -console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); + const result = await client.run( + `Go to growtherapy.com and search for therapists in {{${location}}} ` + + `who specialize in {{${specialty}}} and accept insurance. ` + + `Return the first 5 provider profiles as JSON.`, + { workspaceId: workspace.id, schema: ProviderSearch }, + ); + console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); } } ``` @@ -2589,7 +4073,6 @@ Source: https://docs.browser-use.com/cloud/legacy/agent | Model | API String | Cost per Step | | ----- | ---------- | ------------- | | Browser Use 2.0 (default) | `browser-use-2.0` | \$0.006 | -| Browser Use LLM | `browser-use-llm` | \$0.002 | | O3 | `o3` | \$0.03 | | Gemini Flash Latest | `gemini-flash-latest` | \$0.0075 | | Gemini Flash Lite Latest | `gemini-flash-lite-latest` | \$0.005 | @@ -2623,15 +4106,15 @@ client = AsyncBrowserUse() session = await client.sessions.create() upload = await client.files.session_url( -session.id, -file_name="input.pdf", -content_type="application/pdf", -size_bytes=1024, + session.id, + file_name="input.pdf", + content_type="application/pdf", + size_bytes=1024, ) with open("input.pdf", "rb") as f: -async with httpx.AsyncClient() as http: - await http.post(upload.url, content=f.read(), headers={"Content-Type": "application/pdf"}) + async with httpx.AsyncClient() as http: + await http.post(upload.url, content=f.read(), headers={"Content-Type": "application/pdf"}) result = await client.run("Summarize the uploaded PDF", session_id=session.id) ``` @@ -2664,8 +4147,8 @@ const result = await client.run("Summarize the uploaded PDF", { sessionId: sessi ```python Python result = await client.tasks.get(task_id) for file in result.output_files: -output = await client.files.task_output(task_id, file.id) -print(output.download_url) # download URL + output = await client.files.task_output(task_id, file.id) + print(output.download_url) # download URL ``` ```typescript TypeScript const result = await client.tasks.get(taskId); @@ -2687,8 +4170,8 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() run = client.run("Find the most upvoted post on Reddit r/technology today") async for step in run: -print(f"Step {step.number}: {step.next_goal}") -print(f" URL: {step.url}") + print(f"Step {step.number}: {step.next_goal}") + print(f" URL: {step.url}") print(run.result.output) # final result after iteration ``` @@ -2769,8 +4252,8 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() skill = await client.skills.create( -goal="Extract the top X posts from HackerNews. For each post return: title, URL, score, author, comment count, and rank. X is an input parameter.", -agent_prompt="Go to https://news.ycombinator.com, click on the first post to load its content, go back to the list, and scroll down to trigger loading of additional posts.", + goal="Extract the top X posts from HackerNews. For each post return: title, URL, score, author, comment count, and rank. X is an input parameter.", + agent_prompt="Go to https://news.ycombinator.com, click on the first post to load its content, go back to the list, and scroll down to trigger loading of additional posts.", ) print(skill.id) ``` @@ -2791,8 +4274,8 @@ Skill creation takes ~30 seconds. You can also create skills visually from the [ ```python Python result = await client.skills.execute( -skill.id, -parameters={"X": 10}, + skill.id, + parameters={"X": 10}, ) print(result) ``` @@ -2864,9 +4347,9 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Log into my Jira account and create a new ticket", -op_vault_id="your-vault-id", -allowed_domains=["*.atlassian.net"], + "Log into my Jira account and create a new ticket", + op_vault_id="your-vault-id", + allowed_domains=["*.atlassian.net"], ) print(result.output) ``` @@ -2877,8 +4360,8 @@ const client = new BrowserUse(); const result = await client.run( "Log into my Jira account and create a new ticket", { -opVaultId: "your-vault-id", -allowedDomains: ["*.atlassian.net"], + opVaultId: "your-vault-id", + allowedDomains: ["*.atlassian.net"], }, ); console.log(result.output); @@ -2888,17 +4371,17 @@ For SSO/OAuth redirects, include all required domains: ```python Python result = await client.run( -"Log into Jira and create a ticket for the Q4 release", -op_vault_id="your-vault-id", -allowed_domains=["*.atlassian.net", "*.okta.com"], + "Log into Jira and create a ticket for the Q4 release", + op_vault_id="your-vault-id", + allowed_domains=["*.atlassian.net", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into Jira and create a ticket for the Q4 release", { -opVaultId: "your-vault-id", -allowedDomains: ["*.atlassian.net", "*.okta.com"], + opVaultId: "your-vault-id", + allowedDomains: ["*.atlassian.net", "*.okta.com"], }, ); ``` @@ -2926,9 +4409,9 @@ from browser_use_sdk import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Log into GitHub and star the browser-use/browser-use repo", -secrets={"github.com": "username:password123"}, -allowed_domains=["github.com"], + "Log into GitHub and star the browser-use/browser-use repo", + secrets={"github.com": "username:password123"}, + allowed_domains=["github.com"], ) ``` ```typescript TypeScript @@ -2938,8 +4421,8 @@ const client = new BrowserUse(); const result = await client.run( "Log into GitHub and star the browser-use/browser-use repo", { -secrets: { "github.com": "username:password123" }, -allowedDomains: ["github.com"], + secrets: { "github.com": "username:password123" }, + allowedDomains: ["github.com"], }, ); ``` @@ -2950,28 +4433,85 @@ For SSO/OAuth redirects, include all domains in the auth flow: ```python Python result = await client.run( -"Log into the company portal and download the Q4 report", -secrets={ - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowed_domains=["portal.example.com", "*.okta.com"], + "Log into the company portal and download the Q4 report", + secrets={ + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowed_domains=["portal.example.com", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into the company portal and download the Q4 report", { -secrets: { - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowedDomains: ["portal.example.com", "*.okta.com"], + secrets: { + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowedDomains: ["portal.example.com", "*.okta.com"], }, ); ``` +# API Reference +Source: https://docs.browser-use.com/cloud/api-v4-overview + + +## Authentication + +All requests require an API key in the `X-Browser-Use-API-Key` header: + +``` +X-Browser-Use-API-Key: bu_your_key_here +``` + +Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. + +## Base URL + +``` +https://api.browser-use.com/api/v4 +``` + +## The core loop + +Create a run, poll its status until terminal, then fetch the full result. `status` is a cheap indexed lookup — poll it, not the full run. + +```bash Create a run +curl -X POST https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"task": "Find the top 3 trending repos on GitHub today"}' +``` + +```bash Poll status until completed | failed | cancelled (replace RUN_ID) +curl https://api.browser-use.com/api/v4/runs/RUN_ID/status \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +```bash Fetch the full run once it's terminal +curl https://api.browser-use.com/api/v4/runs/RUN_ID \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +## Sessions and follow-ups + +A run belongs to a session (a conversation). Send a follow-up message to a session's queue — it runs as the next turn, or immediately with `interrupt: true`: + +```bash Queue a follow-up (replace SESSION_ID) +curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"text": "Now open the top result", "interrupt": false}' +``` + +## SDKs + +The [Cloud SDK](https://docs.browser-use.com/cloud/sdk) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. + + # API Reference Source: https://docs.browser-use.com/cloud/api-reference @@ -3061,65 +4601,3 @@ pip install browser-use-sdk npm install browser-use-sdk ``` - -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/llms.txt b/docs/llms.txt index 67bb5b51..66c54da2 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -22,11 +22,14 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started +- [Introduction](https://docs.browser-use.com/cloud/introduction): One API for AI browser agents and stealth cloud browsers. Choose how much control you want. +- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud): The library and the cloud are different products that combine. Here's which one you want. - [Quick start](https://docs.browser-use.com/cloud/quickstart): State-of-the-art AI browser automation with stealth browsers, CAPTCHA solving, residential proxies, and managed infrastructure. -- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How an AI agent can complete the Browser Use agent challenge to get a free account and API key. +- [Pricing & free tier](https://docs.browser-use.com/cloud/pricing): Browser Use Cloud plans, the free tier (no card required), and every usage-based rate. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent +- [Overview](https://docs.browser-use.com/cloud/agent/overview): The hosted agent takes a task in plain language and drives a stealth browser until it's done. - [Introduction](https://docs.browser-use.com/cloud/agent/quickstart): Easiest way to automate the web. Tell this agent in natural language what it should do, and it can interact with the web like a human. - [Models](https://docs.browser-use.com/cloud/agent/models): Choose the right model for your task. - [Structured output](https://docs.browser-use.com/cloud/agent/structured-output): Get validated, typed data back from agent tasks. @@ -35,29 +38,47 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Upload files for the agent, download files the agent creates. - [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Run a task once, then re-execute it for $0 LLM cost. - [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Let a human interact with the live browser while the agent is running. Useful for approvals, payments, complex auth flows, or reviewing agent work before continuing. +- [Performance & speed](https://docs.browser-use.com/cloud/agent/performance): Every setting that affects agent latency, from model choice to typing speed. ## Browser +- [Overview](https://docs.browser-use.com/cloud/browser/overview): Remote stealth browsers you control over CDP. What they are and when to use one. +- [Create a browser session](https://docs.browser-use.com/cloud/browser/create): Every way to start a cloud browser: SDK, REST, or a single WebSocket URL, with all parameters and the response schema. +- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions): Session lifecycle: states, timeouts, stopping, disconnect behavior, and what you're billed for. - [Introduction Stealth](https://docs.browser-use.com/cloud/browser/stealth): Best stealth on the planet. We fork Chromium to give agents access to all websites. +- [CAPTCHA Solving](https://docs.browser-use.com/cloud/browser/captcha): Browser Use remote browsers solve CAPTCHAs automatically, on by default, on every plan. - [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Residential proxies in 195+ countries. On by default. +- [Screenshots](https://docs.browser-use.com/cloud/browser/screenshots): Take viewport and full-page screenshots from a cloud browser session, and control where they're saved. - [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview): Watch the agent's browser in real time. Embed it in your app. -- [Playwright, Puppeteer, Selenium](https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium): Connect your automation framework to Browser Use's stealth infrastructure via CDP. +- [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent): Run the open-source Browser Use agent on a cloud stealth browser. Your code, our infrastructure. + +## Automation frameworks +- [Playwright](https://docs.browser-use.com/cloud/browser/playwright): Connect Playwright to a remote stealth browser over CDP — Python and TypeScript. +- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer): Connect Puppeteer to a remote stealth browser with browserWSEndpoint. +- [Selenium](https://docs.browser-use.com/cloud/browser/selenium): Run Selenium-style automation on Browser Use's stealth browsers — and why to bridge through CDP. ## Authentication - [Profiles](https://docs.browser-use.com/cloud/guides/authentication): Persistent browser state — cookies, localStorage, saved passwords. Login once, reuse across sessions. -- [Sync local and cloud cookies](https://docs.browser-use.com/cloud/guides/profile-sync): Sync your local browser cookies to the cloud — instantly authenticate without managing credentials. +- [Profiles / Cookie sync](https://docs.browser-use.com/cloud/guides/profile-sync): Profiles carry cookies and login state across sessions — sync them from your local browser or reuse them in the cloud. - [2FA](https://docs.browser-use.com/cloud/guides/2fa): Best practices for handling two-factor authentication in automated browser sessions. ## More - [FAQ](https://docs.browser-use.com/cloud/faq): Common questions and solutions. -## Integrations -- [OpenClaw](https://docs.browser-use.com/cloud/tutorials/integrations/openclaw): Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill. +## Platform features - [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. - [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. +- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. + +## Integrations +- [Claude Code](https://docs.browser-use.com/cloud/tutorials/integrations/claude-code): Give Claude Code cloud browser automation with Browser Use. +- [Claude Managed Agents](https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents): Give Anthropic's Claude Managed Agents a stealth cloud browser via the Browser Use CLI. +- [OpenClaw](https://docs.browser-use.com/cloud/tutorials/integrations/openclaw): Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. - [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. ## Tutorials - [Chat UI](https://docs.browser-use.com/cloud/tutorials/chat-ui): Full end-to-end example. Build a chat UI with live browser preview, follow-up tasks, recording, and streaming messages. +- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. - [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) @@ -67,70 +88,12 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [1Password & 2FA](https://docs.browser-use.com/cloud/guides/1password): Auto-fill passwords and TOTP codes from 1Password during agent tasks. - [Secrets](https://docs.browser-use.com/cloud/guides/secrets): Pass domain-scoped credentials to the agent securely. +## API v4 +- [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. + ## API v3 - [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/open-source/llms-full.txt b/docs/open-source/llms-full.txt index 267626a7..8e93d0fc 100644 --- a/docs/open-source/llms-full.txt +++ b/docs/open-source/llms-full.txt @@ -192,6 +192,8 @@ Browser Use natively supports 15+ LLM providers. Most providers accept any model `ChatBrowserUse()` is our optimized in-house model, matching the accuracy of top models while completing tasks **3-5x** faster. [See our blog post→](https://browser-use.com/posts/speed-matters) + Read the [bu-2-0 model card](/open-source/bu-2-0-model-card) for details on intended use, inputs and outputs, tools, benchmarks, judge setup, and limitations. + ```python from browser_use import Agent, ChatBrowserUse @@ -738,13 +740,13 @@ Any provider with an OpenAI-compatible endpoint works via `ChatOpenAI` with a cu Source: https://docs.browser-use.com/open-source/browser-use-cli -The Browser Use CLI (`browser-use`) is the command-line interface for the Browser Use platform. It uses [Browser Harness](https://github.com/browser-use/browser-harness) to allow for: +The Browser Use CLI (`browser-use`) gives coding agents a direct browser-control surface backed by [Browser Harness](https://github.com/browser-use/browser-harness): - **Direct browser control** — agents run Python to do actions in the browser. - **Three browser modes** — you can use with local Chrome or Chromium with your existing tabs, cookies, extensions, and logins; Browser Use cloud browsers; or any browser reachable through a CDP endpoint. - **Agent-ready setup** — install the skill into Claude Code, Codex, and other coding agents so they know when and how to call the CLI. -To try the hosted agent directly, use [Browser Use Cloud](https://cloud.browser-use.com?utm_source=docs&utm_medium=browser-use-cli&utm_campaign=v4), or install the skill. +Try out Browser Use CLI with an agent in [Browser Use Cloud](https://cloud.browser-use.com?utm_source=docs&utm_medium=browser-use-cli&utm_campaign=v4), or install the skill to try it yourself locally. ## Install the CLI @@ -868,9 +870,7 @@ Give this employee admin permission in Azure. Research the top Hacker News stories and summarize the points. ``` -