diff --git a/docs/cloud/agent-signup.mdx b/docs/cloud/agent-signup.mdx index 62fa8b8e6..0e3227b83 100644 --- a/docs/cloud/agent-signup.mdx +++ b/docs/cloud/agent-signup.mdx @@ -67,16 +67,16 @@ Response: Use the returned key for Browser Use Cloud API requests. -For example, create a browser session: +For example, create an API V4 run: ```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ +curl https://api.browser-use.com/api/v4/runs \ -H "X-Browser-Use-API-Key: bu_..." \ -H "Content-Type: application/json" \ - -d '{}' + -d '{"task":"Find the top Hacker News story"}' ``` -See the [Create Browser Session API reference](/cloud/api-v3/browsers/create-browser-session). +See the [API V4 quick start](/cloud/agent/quickstart). ## Claim the account diff --git a/docs/cloud/agent/cache-script.mdx b/docs/cloud/agent/cache-script.mdx deleted file mode 100644 index 6f8509209..000000000 --- a/docs/cloud/agent/cache-script.mdx +++ /dev/null @@ -1,280 +0,0 @@ ---- -title: Deterministic rerun -description: "Run a task once, then re-execute it for $0 LLM cost." -icon: bolt ---- - -Deterministic rerun lets you run a browser task once with a full agent, then **re-execute the same task instantly** using a cached script — no LLM, up to 99% cheaper. - -## Quick start - -Use `@{{double brackets}}` around values that can change between runs. The first call runs the full agent. Every subsequent call with the same template uses the cached script. - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -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), -) - -# 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), -) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-scraper" }); - -// First call — agent explores, creates script (~$0.10, ~60s) -const result = await client.run( - "Get the top @{{5}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); - -// Second call — cached script, different param ($0 LLM, ~5s) -const result2 = await client.run( - "Get the top @{{10}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); -``` - - -## How it works - - - - The brackets mark which parts are parameters: - - ``` - "Get prices from @{{example.com}} for @{{electronics}}" - ``` - - - `@{{example.com}}` → parameter 1 - - `@{{electronics}}` → parameter 2 - - The system strips the values to create a **template**: `"Get prices from @{{}} for @{{}}"`. - - - Template `"Get prices from @{{}} for @{{}}"` is hashed to a unique ID like `a7f3b2c1`. - The system checks the workspace for `scripts/a7f3b2c1.py`. - - - If no script exists, the full agent runs your task. After completing it, the agent saves a standalone Python script that reproduces the result deterministically — no AI needed. - - - If the script exists, it runs directly with the new parameter values. No agent, no LLM. Just the script in a sandbox with browser and proxy. - - - -## Auto-detection - -Caching activates **automatically** when both conditions are met: -- The task contains `@{{` and `}}` -- A `workspace_id` is provided - -No extra flags needed. You can override with `cache_script`: - -| Value | Behavior | -|-------|----------| -| `None` (default) | Auto-detect from `@{{brackets}}` + workspace | -| `True` | Force-enable, even without brackets | -| `False` | Force-disable, even if brackets are present | - -## Examples - -### Parameterized scraping - -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), -) - -# 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}") -``` -```typescript TypeScript -// Agent figures out how to scrape intro.co on first call -let result = await client.run( - "Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", - { workspaceId: workspace.id }, -); - -// 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 }, - ); - console.log(`${keyword}: ${result.output}`); -} -``` - - -### No parameters — cache the exact task - -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), -) - -# Same task again — cached -result2 = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - workspace_id=str(workspace.id), -) -``` -```typescript TypeScript -let result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); - -// Same task again — cached -result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); -``` - - -### Multiple parameters - - -```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), -) - -# 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), -) -``` -```typescript TypeScript -let result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", - { workspaceId: workspace.id }, -); - -// Different countries — cached -result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", - { workspaceId: workspace.id }, -); -``` - - -### Force enable / disable - - -```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, -) - -# Force-disable even with brackets -result = await client.run( - "Explain what @{{templates}} means in Jinja", - workspace_id=str(workspace.id), - cache_script=False, -) -``` -```typescript TypeScript -// Force-enable without brackets -let result = await client.run( - "Get the top stories from Hacker News", - { workspaceId: workspace.id, cacheScript: true }, -); - -// Force-disable even with brackets -result = await client.run( - "Explain what @{{templates}} means in Jinja", - { workspaceId: workspace.id, cacheScript: false }, -); -``` - - -## Inspecting cached scripts - -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)") - -# Download a script to inspect it -await client.workspaces.download(workspace.id, "scripts/a7f3b2c1.py", to="./my_script.py") -``` -```typescript TypeScript -const files = await client.workspaces.files(workspace.id, { prefix: "scripts/" }); -for (const f of files.files) { - console.log(`${f.path} (${f.size} bytes)`); -} -``` - - -## Auto-healing - -Cached scripts can break when a website changes its layout, adds new elements, or alters its structure. Auto-healing detects these failures and automatically regenerates the script. - -### How it works - -When a cached script runs, the system validates its output: - -1. **Fast checks** (no LLM) — detects empty results, error fields in JSON, or exception keywords in output. -2. **LLM judge** — if fast checks pass, a lightweight model validates whether the output looks correct for the original task. -3. **Heal** — if validation fails, the full agent re-runs the task and saves an updated script. - -Auto-healing is **limited to 1 attempt per run** to prevent runaway costs. If the healed script also fails, the output is returned as-is. - -### Cost impact - -| Scenario | LLM cost | -|----------|----------| -| Cached script succeeds | **$0** | -| Cached script fails, auto-heals | ~$0.05–1.00 (one full agent run) | -| Healed script also fails | Same as above (returns best-effort output) | - -Auto-healing is enabled by default for all cached scripts. No configuration needed. - -## Cost comparison - -| | LLM cost | Browser + proxy | Time | -|---|---|---|---| -| First call (agent) | ~$0.05–1.00 | Yes | ~30–120s | -| Cached calls | **$0** | Yes | ~3–10s | - - -The browser and proxy still run for cached calls (the script may need them), so there is a small infrastructure cost per execution. LLM cost drops to zero. - diff --git a/docs/cloud/agent/follow-up-tasks.mdx b/docs/cloud/agent/follow-up-tasks.mdx deleted file mode 100644 index e95fd049b..000000000 --- a/docs/cloud/agent/follow-up-tasks.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Follow-up tasks -description: "Run multiple tasks in the same browser session." -icon: list-check ---- - -When you pass a `session_id`, the session automatically stays alive between tasks. Each task runs a new agent that reuses the same browser — the agents don't share context, but the browser state (page, cookies, tabs) carries over. - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# Create a session, then run tasks inside it -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, -) -result2 = await client.run( - "Extract the customer reviews", - session_id=session.id, -) - -await client.sessions.stop(session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); - -// Create a session, then run tasks inside it -const session = await client.sessions.create(); - -const result1 = await client.run("Go to amazon.com, search for laptops, and open the first result", { - sessionId: session.id, -}); -const result2 = await client.run("Extract the customer reviews", { - sessionId: session.id, -}); - -await client.sessions.stop(session.id); -``` - - -`sessions.create()` returns a `live_url` you can embed to watch each task execute — see [Live preview](/cloud/browser/live-preview). To stream messages as each task runs, use `client.run()` with `for await` — see [Live messages](/cloud/agent/streaming). - - - Sessions time out after 15 minutes of inactivity by default. The maximum session duration is 4 hours. - diff --git a/docs/cloud/agent/human-in-the-loop.mdx b/docs/cloud/agent/human-in-the-loop.mdx index 3ed8a2aed..816f4047b 100644 --- a/docs/cloud/agent/human-in-the-loop.mdx +++ b/docs/cloud/agent/human-in-the-loop.mdx @@ -1,88 +1,61 @@ --- title: Human in the loop -description: "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." +description: "Open the live browser, take over, then continue the same session." icon: hand --- -## Use cases -- Human enters payment info or approves a transaction, agent handles the rest -- Human navigates a complex auth flow, then hands back to agent -- Human reviews what the agent did before the agent continues - - - Sessions time out after 15 minutes of inactivity. The maximum session duration is 4 hours. If the human needs more time, send a lightweight follow-up task (e.g. "wait") to reset the inactivity timer. - - -## Flow - -1. Create a session — it stays alive automatically when you pass `session_id` to `run()` -2. Run an agent task -3. Human interacts with the live browser -4. Send a new follow-up task +Use a human checkpoint for approvals, authentication, payments, or review. +After a run stops, get its `live_view_url` from the `browser.ready` event: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() +from browser_use_sdk.v4 import BrowserUse -# 1. Create a session -session = await client.sessions.create() -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, +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" ) -print(result.output) - -# 3. Human opens live_url and picks a product -input("Press Enter after you've selected a product in the live view...") +run = client.runs.wait_for_completion(run.id) -# 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, +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" ) -print(result.output) +print(ready.data["live_view_url"]) -# Clean up -await client.sessions.stop(session.id) +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, +) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); - -// 1. Create a session -const session = await client.sessions.create(); -console.log(`Live view: ${session.liveUrl}`); - -// 2. Agent does the first part -const searchResult = await client.run( - "Go to amazon.com and search for noise cancelling headphones", - { sessionId: session.id }, -); -console.log(searchResult.output); - -// 3. Human opens liveUrl and picks a product -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => - rl.question("Press Enter after you've selected a product in the live view...", resolve), +const run = await client.runs.create({ + task: "Open the login page and stop for human review", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(run.id); + +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", ); -rl.close(); - -// 4. Agent continues where the human left off -const result = await client.run( - "Get the details of the selected product — name, price, and rating", - { sessionId: session.id }, -); -console.log(result.output); - -// Clean up -await client.sessions.stop(session.id); +console.log(ready?.data.live_view_url); + +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + model: "grok-4.5", + sessionId: run.sessionId, +}); ``` +The same session preserves the conversation and workspace and reuses the live +browser while it is available. Treat live-view URLs as credentials. diff --git a/docs/cloud/agent/models.mdx b/docs/cloud/agent/models.mdx index da9dc2890..cbcb02e17 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -1,88 +1,65 @@ --- title: Models -description: "Choose the right model for your task." +description: "Choose a V4 model and understand its token pricing." icon: microchip --- -Pass `model` to select a model: +Pass one of these API strings as `model` when creating a run: -| Model | API String | Input (per 1M tokens) | Output (per 1M tokens) | -| ----- | ---------- | --------------------- | ---------------------- | -| Claude Sonnet 4.6 | `claude-sonnet-4.6` | \$3.60 | \$18.00 | -| Claude Opus 4.6 | `claude-opus-4.6` | \$6.00 | \$30.00 | -| GPT-5.4 mini | `gpt-5.4-mini` | \$0.90 | \$5.40 | +| Model | API string | Input | Cache read | Output | BYOK | +| ----- | ---------- | ----: | ---------: | -----: | ---- | +| Claude Opus 5 | `claude-opus-5` | \$6.00 | \$0.60 | \$30.00 | Anthropic | +| Grok 4.5 | `grok-4.5` | \$2.40 | \$0.36 | \$7.20 | — | +| GPT-5.6 | `gpt-5.6` | \$6.00 | \$0.60 | \$36.00 | OpenAI | +| Gemini 3.5 Flash | `gemini-3.5-flash` | \$1.80 | \$0.18 | \$10.80 | Google | +| MiniMax M3 | `minimax-m3` | \$0.36 | \$0.072 | \$1.44 | — | + +Token prices are USD per 1 million tokens. Browser sessions +(\$0.02/hour) and network traffic (\$5/GB managed proxy or \$0.20/GB +proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.com/pricing). - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **Grok 4.5** gives the best balance of price and accuracy. **MiniMax M3** is + the default and cheapest option; use **Claude Opus 5** when accuracy matters + most. + + New model strings can reach the API before the TypeScript SDK's generated + union. If TypeScript rejects a model listed above, call `POST /api/v4/runs` + directly for that model. + + ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run( - "List the top 20 posts on Hacker News today with their points", - model="claude-sonnet-4.6", +client = BrowserUse() +run = client.runs.create( + "Compare three project-management tools", + model="grok-4.5", ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const run = await client.runs.create({ + task: "Compare three project-management tools", + model: "grok-4.5", +}); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` ## 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. +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring +Your Own Key**. V4 uses it automatically for matching models; no request flag +is needed. You pay the provider directly, plus a 0.2× Browser Use orchestration +fee. Grok and MiniMax currently use Browser Use-managed keys. diff --git a/docs/cloud/agent/observability.mdx b/docs/cloud/agent/observability.mdx new file mode 100644 index 000000000..1a26eb220 --- /dev/null +++ b/docs/cloud/agent/observability.mdx @@ -0,0 +1,43 @@ +--- +title: Observability +description: "Poll ordered V4 events to monitor a run or build a custom UI." +icon: chart-line +--- + +Poll `runs.events()` with the previous cursor to receive only new events: + + +```python Python +import time + +after = None +while True: + page = client.runs.events(run.id, after=after) + for event in page.events: + print(event.type, event.data) + after = page.next_after if page.next_after is not None else after + + status = client.runs.status(run.id).status.value + if status in {"completed", "failed", "cancelled"}: + break + time.sleep(1) +``` +```typescript TypeScript +let after: number | undefined; +while (true) { + const page = await client.runs.events(run.id, { after }); + for (const event of page.events) { + console.log(event.type, event.data); + } + after = page.nextAfter ?? after; + + const { status } = await client.runs.status(run.id); + if (["completed", "failed", "cancelled"].includes(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); +} +``` + + +Events cover run lifecycle, model calls, browser readiness, tool activity, +artifacts, and completion. See [Get run events](/cloud/api-v4/runs/get-run-events) +for the complete response shape. diff --git a/docs/cloud/agent/quickstart.mdx b/docs/cloud/agent/quickstart.mdx index 5c170dd0c..3c621975e 100644 --- a/docs/cloud/agent/quickstart.mdx +++ b/docs/cloud/agent/quickstart.mdx @@ -1,46 +1,68 @@ --- -title: Introduction -description: "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." +title: Run a task +description: "Give a high-accuracy browser agent a goal and get the result." icon: rocket --- -The SDK is a thin wrapper around the [API v3 Reference](/cloud/api-reference). Every endpoint in the API reference is available as an SDK method — `client.sessions`, `client.browsers`, `client.profiles`, `client.workspaces`, and `client.billing`. +Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: -`client.run()` creates a session, polls every 2 seconds until completion (up to 4 hours), and returns the result. It accepts all parameters from the [Create Session](/cloud/api-v3/sessions/create-session) endpoint. The result is a [Session object](/cloud/api-v3/sessions/get-session) — use `result.output` for the agent's response. +```bash +export BROWSER_USE_API_KEY=your_key +``` ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -**What this agent can do:** -- **Data extraction** — scrape websites with thousands of listings -- **Form filling** — submit applications, fill out surveys, enter data -- **Multi-step workflows** — log in, navigate, click through flows, download files -- **Research** — search across multiple sites, compare results, summarize findings -- **Monitoring** — monitor a website and get notified if something changes -- **Testing** — test websites end-to-end with natural language instructions -- **Scheduling** — schedule tasks to run on a recurring basis -- **1,000+ integrations** — Gmail, Calendar, Notion, and more +Install the SDK with `pip install browser-use-sdk` or +`npm install browser-use-sdk`. Curl needs no installation. + +Every new run implicitly creates a [session](/cloud/agent/sessions) for its +conversation and live browser, plus a [workspace](/cloud/agent/workspaces) for +persistent files. -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +A task starts a run inside a session and the run reads and writes persistent workspace files +A task starts a run inside a session and the run reads and writes persistent workspace files -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. + + + Give the compact context file to your coding agent. + + diff --git a/docs/cloud/agent/scripts.mdx b/docs/cloud/agent/scripts.mdx new file mode 100644 index 000000000..3516dc2ac --- /dev/null +++ b/docs/cloud/agent/scripts.mdx @@ -0,0 +1,46 @@ +--- +title: Scripts +description: "Save tested browser scripts in a workspace and reuse them on later runs." +icon: file-code +--- + +Scripts turn a successful browser run into a reusable +[workspace](/cloud/agent/workspaces) asset. The agent writes and tests the +helper once. Later runs execute it first and repair it only when the site +changes. + +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary + +Create one workspace for the workflow, then use these prompts with the same +`workspace_id` / `workspaceId`. The [run code](/cloud/agent/quickstart) does not +change. + +## First run + +```text +Get the top five Hacker News stories as JSON. + +Then reproduce exactly what you did as helper functions or a script. Test it, +save it in this workspace, and add a README with instructions for using it again. +``` + +## Later runs + +```text +Use the existing workspace script to get the top ten Hacker News stories. +Follow its README. Only fix and retest the script if it no longer works. +``` + +This is faster and cheaper for repeated workflows, but it still starts an agent +and uses tokens. The script is reusable and self-healing—not zero-LLM execution. diff --git a/docs/cloud/agent/sessions.mdx b/docs/cloud/agent/sessions.mdx new file mode 100644 index 000000000..ed43de9d4 --- /dev/null +++ b/docs/cloud/agent/sessions.mdx @@ -0,0 +1,57 @@ +--- +title: Sessions +description: "Continue one conversation across multiple V4 runs." +icon: comments +--- + +A **session** holds the agent's conversation and can reuse its live browser. +One session ID can contain multiple runs. Every run creates a session implicitly +unless you pass an existing session ID. + +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser + +Pass `session_id` / `sessionId` to continue: + + +```python Python +first = client.runs.create("Open Hacker News") +client.runs.wait_for_completion(first.id) + +follow_up = client.runs.create( + "Now summarize the top story", + session_id=first.session_id, +) +result = client.runs.wait_for_completion(follow_up.id) +print(result.result) +``` +```typescript TypeScript +const first = await client.runs.create({ + task: "Open Hacker News", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(first.id); + +const followUp = await client.runs.create({ + task: "Now summarize the top story", + model: "grok-4.5", + sessionId: first.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); +``` + + +Omit the session ID for a new conversation. Pass only a [workspace +ID](/cloud/agent/workspaces) when you want a fresh conversation that keeps the +same files. diff --git a/docs/cloud/agent/streaming.mdx b/docs/cloud/agent/streaming.mdx deleted file mode 100644 index 5fa5520eb..000000000 --- a/docs/cloud/agent/streaming.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: Live messages -description: "Stream the agent's messages in real time to build custom UIs or monitor progress." -icon: message-lines ---- - - - Want a ready-made UI? See the [Chat UI tutorial](/cloud/tutorials/chat-ui). - - -Stream messages as the agent works — reasoning, tool calls, browser actions, and results. Each message has `role`, `type`, `summary`, `data`, and `screenshot_url`. See [List session messages](/cloud/api-v3/sessions/list-session-messages) for all fields. - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -run = client.run("Find the top story on Hacker News") -async for msg in run: - print(f"[{msg.role}] {msg.summary}") - -print(run.result.output) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); - -const run = client.run("Find the top story on Hacker News"); -for await (const msg of run) { - console.log(`[${msg.role}] ${msg.summary}`); -} - -console.log(run.result.output); -``` - - -``` -[user] Find the top story on Hacker News -[assistant] Navigating to https://news.ycombinator.com/ -[tool] Browser Navigate: Navigated -[assistant] Analyzing browser state -[tool] Browser Analyze State: The top story is "Coding Agents Could Make Free Software Matter Again" -[tool] Done Autonomous: The top story on Hacker News is "Coding Agents Could Make Free Software Matter Again" -``` - -## Cancel a running task - -Use `stop(strategy="task")` to cancel the current task without destroying the session. The session goes back to `idle` and can accept a new task. - - -```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 -# 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; - } -} -// Session is now idle — send a different task or close it -``` - - - - `run.result` is only available **after** the iterator finishes (all messages consumed or task completes). If you break early from `async for` / `for await`, the task may still be running — call `stop(strategy="task")` to cancel it before sending a follow-up. - - -## Manual polling - -If you need full control over the polling loop (e.g. custom interval, filtering): - - -```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -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 - - 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) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Find the top story on Hacker News", -}); - -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; - } - - const s = await client.sessions.get(session.id); - if (["idle", "stopped", "error", "timed_out"].includes(s.status)) { - console.log(s.output); - break; - } - await new Promise((r) => setTimeout(r, 2000)); -} -``` - - -## Related - -- [Live preview & recording](/cloud/browser/live-preview) — embed the browser alongside your message stream -- [Follow-up tasks](/cloud/agent/follow-up-tasks) — chain multiple tasks in one session while streaming each diff --git a/docs/cloud/agent/structured-output.mdx b/docs/cloud/agent/structured-output.mdx index 9a0abe0b8..dc992fac4 100644 --- a/docs/cloud/agent/structured-output.mdx +++ b/docs/cloud/agent/structured-output.mdx @@ -1,57 +1,48 @@ --- title: Structured output -description: "Get validated, typed data back from agent tasks." +description: "Ask for JSON and validate the V4 result in your application." icon: table --- -Pass a Pydantic model (Python) or Zod schema (TypeScript) — `result.output` is automatically validated and converted to the typed object. - - - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. - +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel -class Post(BaseModel): - name: str - points: int - comments: int +client = BrowserUse() -class HNPosts(BaseModel): - posts: list[Post] +class Story(BaseModel): + title: str + points: int -client = AsyncBrowserUse() -result = await client.run( - "List the top 20 posts on Hacker News today with their points", - output_schema=HNPosts, +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -for post in result.output.posts: - print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const Post = z.object({ - name: z.string(), +const client = new BrowserUse(); + +const Story = z.object({ + title: z.string(), points: z.number(), - comments: z.number(), }); -const HNPosts = z.object({ - posts: z.array(Post), +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); - -const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { schema: HNPosts }, -); -for (const post of result.output.posts) { - console.log(`${post.name} (${post.points} pts, ${post.comments} comments)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` + +V4 does not accept `output_schema` / `outputSchema`. Handle validation errors +and retry with a [session follow-up](/cloud/agent/sessions) when needed. diff --git a/docs/cloud/agent/workspaces.mdx b/docs/cloud/agent/workspaces.mdx index f4f51eeed..cc0cda7c3 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -1,198 +1,88 @@ --- title: Workspaces & files -description: "Upload files for the agent, download files the agent creates." +description: "Persist files across V4 runs and conversations." icon: folder --- -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace -## Upload a file +## Upload and attach a file ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +client = BrowserUse() +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -# Upload -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", +run = client.runs.create( + "Find everyone in the CSV who works at Google", workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Upload -await client.workspaces.upload(workspace.id, "people.csv"); - -// Agent can now read it -const result = await client.run( - "Read people.csv and tell me who works at Google", - { workspaceId: workspace.id }, +const workspace = await client.workspaces.create({ + name: "research", +}); +const uploaded = await client.workspaces.upload( + workspace.id, + "people.csv", ); -console.log(result.output); + +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + model: "grok-4.5", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` -You can upload multiple files at once: +Attachments are run-scoped. Reusing a workspace does not reattach every upload. - -```python Python -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png") -``` -```typescript TypeScript -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png"); -``` - +## Retrieve created files -## Download files +Ask the agent to save its output, then list the workspace: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -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, +files = client.workspaces.files( + workspace.id, + include_urls=True, ) - -# Download a single file -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}") +for file in files.files: + print(file.path, file.url) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Agent creates a file -const result = await client.run( - "Go to Hacker News and save the top 3 posts as posts.json", - { workspaceId: workspace.id }, +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, ); - -// Download a single file -await client.workspaces.download(workspace.id, "posts.json", { to: "./posts.json" }); - -// Or download everything -const paths = await client.workspaces.downloadAll(workspace.id, { to: "./output" }); -for (const p of paths) { - console.log(`Downloaded: ${p}`); -} -``` - - -## Manage workspaces - - -```python Python -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) -await client.workspaces.delete(workspace_id) -``` -```typescript TypeScript -const workspace = await client.workspaces.get(workspaceId); -const updated = await client.workspaces.update(workspaceId, { name: "renamed" }); -const response = await client.workspaces.list(); -for (const w of response.items) { - console.log(w.id, w.name); +for (const file of files.files) { + console.log(file.path, file.url); } -await client.workspaces.delete(workspaceId); ``` -## Organize with prefixes - -Use `prefix` to organize files into directories within a workspace: - - -```python Python -# Upload into a subdirectory -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) - -# Download only files from a subdirectory -await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") -``` -```typescript TypeScript -// Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", { prefix: "reports/" }); - -// List files in a subdirectory -const files = await client.workspaces.files(workspace.id, { prefix: "reports/" }); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Download only files from a subdirectory -await client.workspaces.downloadAll(workspace.id, { to: "./output", prefix: "reports/" }); -``` - - -## List and delete files - - -```python Python -# List all files -files = await client.workspaces.files(workspace.id) -for f in files.files: - print(f.path, f.size) - -# Delete a single file -await client.workspaces.delete_file(workspace.id, path="old-report.pdf") - -# Check workspace storage usage -size = await client.workspaces.size(workspace.id) -print(f"Used: {size.used_bytes} bytes") -``` -```typescript TypeScript -// List all files -const files = await client.workspaces.files(workspace.id); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Delete a single file -await client.workspaces.deleteFile(workspace.id, "old-report.pdf"); - -// Check workspace storage usage -const size = await client.workspaces.size(workspace.id); -console.log(`Used: ${size.usedBytes} bytes`); -``` - - -## Cloud dashboard - -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). - - - Deleting a workspace permanently removes all its files. This cannot be undone. - +Download URLs expire after 60 seconds. See the [workspace API +reference](/cloud/api-v4/workspaces/list-workspace-files) for pagination and +limits. diff --git a/docs/cloud/api-v4-overview.mdx b/docs/cloud/api-v4-overview.mdx index 0e232acf8..bf9e78881 100644 --- a/docs/cloud/api-v4-overview.mdx +++ b/docs/cloud/api-v4-overview.mdx @@ -55,4 +55,4 @@ curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ ## SDKs -The [Cloud SDK](/cloud/sdk) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. +The [Cloud SDK quick start](/cloud/agent/quickstart) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. diff --git a/docs/cloud/browser/live-preview.mdx b/docs/cloud/browser/live-preview.mdx index 5a67737f8..f3d8ebcef 100644 --- a/docs/cloud/browser/live-preview.mdx +++ b/docs/cloud/browser/live-preview.mdx @@ -1,153 +1,88 @@ --- title: Live preview & recording -description: "Watch the agent's browser in real time. Embed it in your app." +description: "Watch an API V4 run in real time or record its browser." icon: eye --- - - Want a ready-made UI? See the [Chat UI tutorial](/cloud/tutorials/chat-ui). - - -`liveUrl` is returned on session creation. +The `browser.ready` event contains the live browser URL: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) -client = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" +) +print(ready.data["live_view_url"]) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Check how many GitHub stars browser-use has", +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", }); -console.log(session.liveUrl); -``` - - -`liveUrl` is also returned when creating a standalone browser session: +await client.runs.waitForCompletion(run.id); - -```python Python -browser = await client.browsers.create() -print(browser.live_url) -``` -```typescript TypeScript -const browser = await client.browsers.create(); -console.log(browser.liveUrl); +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", +); +console.log(ready?.data.live_view_url); ``` -## Embed live browser into your app - -Useful for human interaction or to see live what's happening. - -```html - -``` - -The live URL is hosted on `live.browser-use.com`. If your app sets a Content Security Policy, add it to your `frame-src` directive: - -``` -Content-Security-Policy: frame-src 'self' https://live.browser-use.com; -``` +Poll [run events](/cloud/agent/observability) if you need the URL as soon as +the browser starts. -For responsive sizing, use CSS instead of fixed dimensions: +## Embed the live browser ```html ``` -## Customize - -Append query parameters to the `liveUrl`: - -| Parameter | Values | Description | -|-----------|--------|-------------| -| `theme` | `light`, `dark` (default) | Light or dark mode | -| `ui` | `false` | Hide the browser chrome (URL bar, tabs) | - -``` -https://live.browser-use.com?wss=...&theme=light -https://live.browser-use.com?wss=...&ui=false -``` +The URL is hosted on `live.browser-use.com`. Add that origin to your +Content Security Policy's `frame-src` directive when needed. Treat the URL as +a credential: anyone with it can interact with the active browser. ## Recording - - `waitForRecording` / `wait_for_recording` requires the **v3 SDK** (`from browser_use_sdk.v3 import AsyncBrowserUse` / `import { BrowserUse } from "browser-use-sdk/v3"`). - - -Enable recording to get an MP4 video of the browser session. Only available when the agent actually opens a browser — tasks answered without browsing produce no recording. If you run multiple tasks in the same session (with `keep_alive`), you may get multiple recordings. +Enable recording when the run creates its browser: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -result = await client.run( - "Check how many GitHub stars browser-use has", - enable_recording=True, +run = client.runs.create( + "Test the checkout flow", + browser_settings={"record": 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 ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const result = await client.run("Check how many GitHub stars browser-use has", { - enableRecording: true, +const run = await client.runs.create({ + task: "Test the checkout flow", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, }); - -// Waits up to 15s for recording to be ready. Returns [] if no browser was opened. -const urls = await client.sessions.waitForRecording(result.id); -for (const url of urls) { - console.log(url); // presigned MP4 download URL -} ``` - - -For standalone browser sessions, pass `enable_recording` when creating the browser and retrieve the URL after stopping it: - - -```python Python -browser = await client.browsers.create(enable_recording=True) -# ... use the browser via CDP ... -stopped = await client.browsers.stop(browser.id) -print(stopped.recording_url) # presigned MP4 download URL -``` -```typescript TypeScript -const browser = await client.browsers.create({ enableRecording: true }); -// ... use the browser via CDP ... -const stopped = await client.browsers.stop(browser.id); -console.log(stopped.recordingUrl); // presigned MP4 download URL +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Test checkout","browserSettings":{"record":true}}' ``` - - Recording URLs are presigned and **expire after 1 hour**. Download or serve the recording promptly. If you need to access it later, save the MP4 to your own storage. - - -## Related - -- [Live messages](/cloud/agent/streaming) — stream the agent's messages alongside the live browser view -- [Follow-up tasks](/cloud/agent/follow-up-tasks) — chain tasks in one session while watching live - +The MP4 becomes available in the Dashboard after the browser stops. API runs +default to recording off, and Zero Data Retention projects never record. diff --git a/docs/cloud/browser/playwright-puppeteer-selenium.mdx b/docs/cloud/browser/playwright-puppeteer-selenium.mdx index 85134290f..dafbf96ed 100644 --- a/docs/cloud/browser/playwright-puppeteer-selenium.mdx +++ b/docs/cloud/browser/playwright-puppeteer-selenium.mdx @@ -1,42 +1,54 @@ --- title: Playwright, Puppeteer, Selenium -description: "Connect your automation framework to Browser Use's stealth infrastructure via CDP." +description: "Control a Browser Use cloud browser directly over CDP." 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. +Every browser runs in a [hardened Chromium fork](/cloud/browser/stealth) with +stealth, anti-fingerprinting, and [residential +proxies](/cloud/browser/proxies) enabled by default. -## Option 1: WebSocket URL (no SDK) + + This page is for direct browser control. To give an AI agent a goal instead, + [create an API V4 run](/cloud/agent/quickstart). + -Connect with a single URL. All configuration is passed as query parameters. +## 1. Create a browser + +```bash +session=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') + +export BROWSER_SESSION_ID=$(echo "$session" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$session" | jq -r .cdpUrl) +``` + +## 2. Connect over CDP ### Playwright ```python Python -from playwright.async_api import async_playwright - -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" +import os +from playwright.sync_api import sync_playwright -async with async_playwright() as p: - browser = await p.chromium.connect_over_cdp(WSS_URL) +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(os.environ["BROWSER_USE_CDP_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 + page.goto("https://example.com") + print(page.title()) ``` ```typescript TypeScript import { chromium } from "playwright"; -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await chromium.connectOverCDP(WSS_URL); +const browser = await chromium.connectOverCDP( + process.env.BROWSER_USE_CDP_URL!, +); const page = browser.contexts()[0].pages()[0]; await page.goto("https://example.com"); console.log(await page.title()); -await browser.close(); -// Browser is automatically stopped when the WebSocket disconnects ``` @@ -45,113 +57,34 @@ await browser.close(); ```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 browser = await puppeteer.connect({ + browserWSEndpoint: process.env.BROWSER_USE_CDP_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 - -| 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. | +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. -## Option 2: SDK +## 3. Stop the browser -Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse -from playwright.async_api import async_playwright - -client = AsyncBrowserUse() -browser = await client.browsers.create() -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() - -await client.browsers.stop(browser.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { chromium } from "playwright"; - -const client = new BrowserUse(); -const browser = await client.browsers.create(); -console.log(browser.cdpUrl); // https://uuid.cdpN.browser-use.com -console.log(browser.liveUrl); // https://live.browser-use.com?wss=... - -const pwBrowser = await chromium.connectOverCDP(browser.cdpUrl); -const page = pwBrowser.contexts()[0].pages()[0]; -await page.goto("https://example.com"); -console.log(await page.title()); -await pwBrowser.close(); - -await client.browsers.stop(browser.id); -``` - - -### Puppeteer - -```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 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/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' ``` - Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. + `browser.close()` and disconnecting CDP do not stop the managed browser. + Call `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. + +See [Create browser session](/cloud/api-v4/browsers/create-browser-session) and +[Update browser session](/cloud/api-v4/browsers/update-browser-session) for +every browser setting and response field. diff --git a/docs/cloud/browser/proxies.mdx b/docs/cloud/browser/proxies.mdx index 6ca1f747d..0acaae70d 100644 --- a/docs/cloud/browser/proxies.mdx +++ b/docs/cloud/browser/proxies.mdx @@ -1,84 +1,121 @@ --- title: Proxies -description: "Residential proxies in 195+ countries. On by default." +description: "Route API V4 agent runs through residential or custom proxies." icon: globe --- -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](/cloud/api-v3/browsers/create-browser-session) for all supported country codes. +A US residential proxy is enabled by default. The browser's traffic passes +through that residential IP before reaching the website, so the website sees +the proxy's location—not your server's. + +A cloud browser routing its traffic through a residential proxy before reaching a website +A cloud browser routing its traffic through a residential proxy before reaching a website + +Set `browser_settings` / `browserSettings` when you create a V4 run to choose +another country: + + + The current TypeScript SDK type requires `proxyCountryCode` whenever + `browserSettings` is present. Use `"us"` to keep the default, or `null` to + disable the managed proxy. + ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -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") +client = BrowserUse() +run = client.runs.create( + "Get the iPhone 16 price on amazon.de", + browser_settings={"proxyCountryCode": "de"}, +) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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" }); +const run = await client.runs.create({ + task: "Get the iPhone 16 price on amazon.de", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "de" }, +}); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Get the iPhone 16 price on amazon.de", + "browserSettings":{"proxyCountryCode":"de"}}' ``` ## Disable proxies -If your use case does not need proxies, for example QA testing. +Pass `null` for QA or internal sites that do not need a residential proxy: ```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) +run = client.runs.create( + "Test my staging site", + browser_settings={"proxyCountryCode": 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 }); +const run = await client.runs.create({ + task: "Test my staging site", + model: "grok-4.5", + browserSettings: { proxyCountryCode: null }, +}); ``` ## Custom proxy -Bring your own proxy server (HTTP or SOCKS5). +Custom HTTP and SOCKS5 proxies are available on paid plans: ```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", +run = client.runs.create( + "Check the account dashboard", + browser_settings={ + "customProxy": { + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + "ignoreCertErrors": False, + } }, ) ``` ```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", +const run = await client.runs.create({ + task: "Check the account dashboard", + model: "grok-4.5", + browserSettings: { + proxyCountryCode: "us", + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + ignoreCertErrors: false, + }, }, }); ``` + +A custom proxy overrides `proxyCountryCode` and must be passed again when a +follow-up provisions a new browser. See the [Create run +reference](/cloud/api-v4/runs/create-run) for the complete settings object. diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx new file mode 100644 index 000000000..11beb3b6f --- /dev/null +++ b/docs/cloud/browser/quickstart.mdx @@ -0,0 +1,103 @@ +--- +title: Browser quickstart +sidebarTitle: Quick start +description: "Launch a cloud browser and connect to it from your code." +icon: rocket +--- + +Every browser includes stealth, proxies, live preview, and recording. Its +**CDP URL** is a WebSocket endpoint for remotely controlling Chrome. + +Your code using a CDP URL to connect to and control a cloud browser that accesses the web +Your code using a CDP URL to connect to and control a cloud browser that accesses the web + +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: + +```bash +export BROWSER_USE_API_KEY=your_key +``` + +## Install the SDK + +Skip this step if you use curl. + + +```bash Python +pip install browser-use-sdk +``` +```bash TypeScript +npm install browser-use-sdk +``` + + +## Launch a browser + + +```python Python +from browser_use_sdk.v3 import BrowserUse + +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) + +# When finished: +client.browsers.stop(browser.id) +``` +```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); + +// When finished: +await client.browsers.stop(browser.id); +``` +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') + +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) +echo "$BROWSER_USE_CDP_URL" + +# When finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` + + + + `browser.close()`, disconnecting CDP, or `client.close()` does not stop the + managed browser. Use `client.browsers.stop(browser.id)` or call + `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. + + + + + Use the CDP URL with Playwright or Puppeteer. + + + Configure proxies, screen size, recording, and timeout. + + diff --git a/docs/cloud/browser/stealth.mdx b/docs/cloud/browser/stealth.mdx index 288f9d08a..0f64772d5 100644 --- a/docs/cloud/browser/stealth.mdx +++ b/docs/cloud/browser/stealth.mdx @@ -1,5 +1,5 @@ --- -title: Introduction Stealth +title: Stealth description: "Best stealth on the planet. We fork Chromium to give agents access to all websites." icon: mask --- @@ -16,4 +16,4 @@ 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. diff --git a/docs/cloud/faq.mdx b/docs/cloud/faq.mdx index 878e9bd33..50d26c1f0 100644 --- a/docs/cloud/faq.mdx +++ b/docs/cloud/faq.mdx @@ -6,19 +6,30 @@ icon: circle-question ## Which model should I use? -- **Claude Opus 4.6** (`claude-opus-4.6`) — most capable. Use for the hardest tasks that need maximum accuracy. -- **Claude Sonnet 4.6** (`claude-sonnet-4.6`, default) — best balance of capability and cost. Use for complex multi-step workflows. -- **GPT-5.4 mini** (`gpt-5.4-mini`) — fast and efficient. Good for simple, well-defined tasks. +- **Claude Opus 5** (`claude-opus-5`) — maximum intelligence for difficult, long-horizon work. +- **GPT-5.6** (`gpt-5.6`) — fast on complex tasks. +- **Gemini 3.5 Flash** (`gemini-3.5-flash`) — fast for simpler tasks. +- **MiniMax M3** (`minimax-m3`, default) — cheapest for simple and high-volume tasks. + +See [Models](/cloud/agent/models) for the complete V4 picker and pricing. ## How do I get the live browser URL? -`live_url` is returned on session creation. Embed it in an iframe or open it in a browser. +The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -session = await client.sessions.create(task="Go to example.com") -print(session.live_url) +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +created = client.runs.create("Go to example.com") +client.runs.wait_for_completion(created.id) +events = client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +print(ready.data["live_view_url"]) ``` +Poll events until `browser.ready` appears if you need the URL while the run is still active. See [Human in the loop](/cloud/agent/human-in-the-loop) for a complete flow. + ## Getting blocked by a website Stealth and proxies are active by default. If you're still getting blocked: @@ -32,21 +43,18 @@ If it still doesn't work, contact support inside the [Cloud Dashboard](https://c The SDK auto-retries 429 responses with exponential backoff. If persistent, you may need more concurrent sessions — contact support. -## v2 vs v3 — which should I use? +## V2 or V4 — which should I use? -**v3 is the recommendation for everything.** It's a premium agent (not available in open source) that is significantly more capable than v2: +Use **V4** for difficult tasks where accuracy matters. It supports: -- **Much better at complex tasks** and multi-step workflows -- **Much better at large data extraction** -- **File system** with persistent memory across tasks -- **Task scheduling** with 1,000+ integrations (Gmail, Slack, and more) +- Run-focused API with a cheap status polling endpoint +- Conversation sessions with follow-ups +- Persistent workspaces and turn-scoped file attachments +- Incremental events for custom UIs and monitoring +- Per-run cost totals, cost caps, and optional judgement -v2 is the closest to the open-source experience — pure browser automation, nothing else. If the open source already works great for your use case, v2 is the natural fit. For everything else, use v3. +Use **V2** when tasks are simple and your priority is very low cost and +predictable speed. Its accuracy is substantially lower. -```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse - -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse -``` +See Browser Use at #1 on the +[Odysseys benchmark](https://odysseysbench.com/leaderboard). diff --git a/docs/cloud/guides/2fa.mdx b/docs/cloud/guides/2fa.mdx index 5d3c02fc2..f75513da5 100644 --- a/docs/cloud/guides/2fa.mdx +++ b/docs/cloud/guides/2fa.mdx @@ -1,283 +1,70 @@ --- title: 2FA -description: "Best practices for handling two-factor authentication in automated browser sessions." +description: "Handle two-factor authentication in API V4 runs." icon: shield-halved --- -Sites with 2FA block automated logins. Here are four approaches — pick the one that fits your setup. +The most reliable options are a saved profile or a human checkpoint. -| Approach | Best for | Complexity | -|---|---|---| -| [Profiles (login once)](#1-profiles--login-once-reuse-cookies) | Sites with long-lived cookies | Lowest | -| [Human in the loop](#2-human-in-the-loop) | One-off tasks, complex auth flows | Low | -| [Agent Mail](#3-agent-mail) | Email-based 2FA, end-client automation | Medium | -| [TOTP secret in prompt](#4-totp-secret-in-prompt) | Authenticator app 2FA (Google Authenticator, Authy) | Medium | +## Reuse a logged-in profile ---- - -## 1. Profiles — login once, reuse cookies - -Login manually once (or let the agent do it), then save the browser state as a profile. Future sessions reuse the cookies — no 2FA prompt as long as the cookies are valid. +[Sync your local login](/cloud/guides/profile-sync), then load that profile in +the run: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# Create a profile and a session -profile = await client.profiles.create(name="my-account") -session = await client.sessions.create(profile_id=profile.id) -print(f"Live view: {session.live_url}") - -# Option A: human logs in via live view -input("Log in and complete 2FA in the live view, then press Enter...") - -# Option B: let the agent log in -# await client.run("Log in to example.com with user@example.com / password123", session_id=session.id) - -# Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id) - -# Next time: reuse the profile, no 2FA needed -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Go to example.com/dashboard and get my balance", 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(); - -// Create a profile and a session -const profile = await client.profiles.create({ name: "my-account" }); -const session = await client.sessions.create({ profileId: profile.id }); -console.log(`Live view: ${session.liveUrl}`); - -// Option A: human logs in via live view -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => rl.question("Log in and complete 2FA in the live view, then press Enter...", resolve)); -rl.close(); - -// Option B: let the agent log in -// await client.run("Log in to example.com with user@example.com / password123", { sessionId: session.id }); - -// Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id); - -// Next time: reuse the profile, no 2FA needed -const newSession = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Go to example.com/dashboard and get my balance", { sessionId: newSession.id }); -console.log(result.output); -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. - - ---- - -## 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, +run = client.runs.create( + "Download my latest invoice", + browser_settings={"profileId": "YOUR_PROFILE_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); +const run = await client.runs.create({ + task: "Download my latest invoice", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, +}); ``` -See [Human in the loop](/cloud/agent/human-in-the-loop) for more patterns. - ---- +This avoids another 2FA challenge while the site's cookies remain valid. -## 3. Agent Mail +## Let a human take over -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. +Ask the first run to stop at the 2FA screen, get its `live_view_url` from the +[`browser.ready` event](/cloud/agent/human-in-the-loop), and have the user enter +the code. Then continue with the same session: ```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 +first = client.runs.create( + "Open the login page and stop at the 2FA prompt", ) -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. +client.runs.wait_for_completion(first.id) -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. - """, +next_run = client.runs.create( + "Continue after login and download the invoice", + session_id=first.session_id, ) -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); +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(first.id); + +const nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + model: "grok-4.5", + sessionId: first.sessionId, +}); ``` -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. - - +See [Human in the loop](/cloud/agent/human-in-the-loop) for retrieving and +embedding the live browser URL. Never put passwords or TOTP secrets directly +in a prompt. diff --git a/docs/cloud/guides/authentication.mdx b/docs/cloud/guides/authentication.mdx index 7b5b776c8..072ac852f 100644 --- a/docs/cloud/guides/authentication.mdx +++ b/docs/cloud/guides/authentication.mdx @@ -1,98 +1,66 @@ --- title: Profiles -description: "Persistent browser state — cookies, localStorage, saved passwords. Login once, reuse across sessions." +description: "Reuse cookies and browser state in API V4 runs." icon: user --- +A profile persists cookies, local storage, and login state across browsers. +Log in once, save the profile, then reuse it to start future browsers already +logged in. + +One login saved as a profile and reused by multiple future browsers +One login saved as a profile and reused by multiple future browsers + +Create or select one under [Dashboard → Profiles](https://cloud.browser-use.com/settings?tab=profiles), +then pass its ID in V4 browser settings: + ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -profile = await client.profiles.create(name="user-id-1") -# or search existing -# profile = (await client.profiles.list(query="user-id-1")).items[0] -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Check browser-use github stars", session_id=session.id) -print(result.output) - -# Always stop the session to persist profile state -await client.sessions.stop(session.id) +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create( + "Open my account dashboard and summarize it", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) +result = client.runs.wait_for_completion(run.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const profile = await client.profiles.create({ name: "user-id-1" }); -// or search existing -// const profile = (await client.profiles.list({ query: "user-id-1" })).items[0]; -const session = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Check browser-use github stars", { - sessionId: session.id, +const run = await client.runs.create({ + task: "Open my account dashboard and summarize it", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); -console.log(result.output); - -// Always stop the session to persist profile state -await client.sessions.stop(session.id); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` - - -View your profile IDs at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=profiles). - -## Manage profiles - - -```python Python -# Create -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) - -# Search by name -response = await client.profiles.list(query="user-id-1") -profile = response.items[0] # first match - -# Get one by ID -profile = await client.profiles.get(profile_id) - -# Update -await client.profiles.update(profile_id, name="renamed") - -# Delete -await client.profiles.delete(profile_id) -``` -```typescript TypeScript -// Create -const profile = await client.profiles.create({ name: "work-account" }); - -// List all -const response = await client.profiles.list(); -for (const p of response.items) { - console.log(p.id, p.name); -} - -// Search by name -const results = await client.profiles.list({ query: "user-id-1" }); -const found = results.items[0]; // first match - -// Get one by ID -const fetched = await client.profiles.get(profileId); - -// Update -await client.profiles.update(profileId, { name: "renamed" }); - -// Delete -await client.profiles.delete(profileId); +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Summarize my account dashboard", + "browserSettings":{"profileId":"YOUR_PROFILE_ID"}}' ``` -## Usage patterns - -- **Per-user profiles:** Create one profile per end-user. Query by name to get the profile ID, or store a mapping between your users and their profile IDs in your database. +Use one profile per end user. Follow-ups in the same [session](/cloud/agent/sessions) +reuse the live browser; later sessions can load the same profile again. - - 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. - +For the fastest setup, [sync an existing local login](/cloud/guides/profile-sync). diff --git a/docs/cloud/guides/profile-sync.mdx b/docs/cloud/guides/profile-sync.mdx index b7edc630e..a23b88e85 100644 --- a/docs/cloud/guides/profile-sync.mdx +++ b/docs/cloud/guides/profile-sync.mdx @@ -1,30 +1,42 @@ --- title: Sync local and cloud cookies -description: "Sync your local browser cookies to the cloud — instantly authenticate without managing credentials." +description: "Sync a local login, then use it in an API V4 run." icon: arrows-rotate --- +Run the profile sync helper: + ```bash -export BROWSER_USE_API_KEY=your_key && curl -fsSL https://browser-use.com/profile.sh | sh +export BROWSER_USE_API_KEY=your_key +curl -fsSL https://browser-use.com/profile.sh | sh ``` -This opens a browser where you select which accounts to sync. After syncing, you receive a `profile_id` to use in your tasks. +Choose the accounts to sync, then use the returned profile ID: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -session = await client.sessions.create(profile_id="your_synced_profile_id") -result = await client.run("Check my LinkedIn messages", session_id=session.id) +client = BrowserUse() +run = client.runs.create( + "Check my LinkedIn messages", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const session = await client.sessions.create({ profileId: "your_synced_profile_id" }); -const result = await client.run("Check my LinkedIn messages", { - sessionId: session.id, +const run = await client.runs.create({ + task: "Check my LinkedIn messages", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` + +The profile supplies cookies and local storage without putting credentials in +the prompt. Re-sync when the site's login expires. diff --git a/docs/cloud/guides/x402.mdx b/docs/cloud/guides/x402.mdx index 9a38defbc..6a2beaf14 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 */} diff --git a/docs/cloud/images/browser-cdp-dark.excalidraw b/docs/cloud/images/browser-cdp-dark.excalidraw new file mode 100644 index 000000000..cc55c238b --- /dev/null +++ b/docs/cloud/images/browser-cdp-dark.excalidraw @@ -0,0 +1,294 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "yourCode", + "x": 55, + "y": 135, + "width": 250, + "height": 130, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29101, + "version": 1, + "versionNonce": 39101, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "yourCodeText", + "x": 55, + "y": 173, + "width": 250, + "height": 54, + "text": "YOUR CODE", + "originalText": "YOUR CODE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29102, + "version": 1, + "versionNonce": 39102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "cdpConnection", + "x": 330, + "y": 200, + "width": 180, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29103, + "version": 1, + "versionNonce": 39103, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 180, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "cdpLabel", + "x": 340, + "y": 123, + "width": 160, + "height": 46, + "text": "CDP URL", + "originalText": "CDP URL", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29104, + "version": 1, + "versionNonce": 39104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "cloudBrowser", + "x": 535, + "y": 95, + "width": 340, + "height": 210, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29105, + "version": 1, + "versionNonce": 39105, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "cloudBrowserText", + "x": 560, + "y": 145, + "width": 290, + "height": 119, + "text": "CLOUD\nBROWSER", + "originalText": "CLOUD\nBROWSER", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29106, + "version": 1, + "versionNonce": 39106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "browserToWeb", + "x": 900, + "y": 200, + "width": 80, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29107, + "version": 1, + "versionNonce": 39107, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 80, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "ellipse", + "id": "web", + "x": 990, + "y": 125, + "width": 150, + "height": 150, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29108, + "version": 1, + "versionNonce": 39108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "webText", + "x": 990, + "y": 173, + "width": 150, + "height": 54, + "text": "WEB", + "originalText": "WEB", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29109, + "version": 1, + "versionNonce": 39109, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-cdp-dark.svg b/docs/cloud/images/browser-cdp-dark.svg new file mode 100644 index 000000000..feeac977e --- /dev/null +++ b/docs/cloud/images/browser-cdp-dark.svg @@ -0,0 +1,28 @@ + + Connect your code to a cloud browser with a CDP URL + Your code uses the CDP URL as a connection address to control a Browser Use cloud browser, which then accesses the web. + + + + + + + + + + + + + + + + + + + YOUR CODE + CDP URL + CLOUD + BROWSER + WEB + + diff --git a/docs/cloud/images/browser-cdp-light.excalidraw b/docs/cloud/images/browser-cdp-light.excalidraw new file mode 100644 index 000000000..9b797ca18 --- /dev/null +++ b/docs/cloud/images/browser-cdp-light.excalidraw @@ -0,0 +1,294 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "yourCode", + "x": 55, + "y": 135, + "width": 250, + "height": 130, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29201, + "version": 1, + "versionNonce": 39201, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "yourCodeText", + "x": 55, + "y": 173, + "width": 250, + "height": 54, + "text": "YOUR CODE", + "originalText": "YOUR CODE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29202, + "version": 1, + "versionNonce": 39202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "cdpConnection", + "x": 330, + "y": 200, + "width": 180, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29203, + "version": 1, + "versionNonce": 39203, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 180, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "id": "cdpLabel", + "x": 340, + "y": 123, + "width": 160, + "height": 46, + "text": "CDP URL", + "originalText": "CDP URL", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29204, + "version": 1, + "versionNonce": 39204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "cloudBrowser", + "x": 535, + "y": 95, + "width": 340, + "height": 210, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29205, + "version": 1, + "versionNonce": 39205, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "cloudBrowserText", + "x": 560, + "y": 145, + "width": 290, + "height": 119, + "text": "CLOUD\nBROWSER", + "originalText": "CLOUD\nBROWSER", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29206, + "version": 1, + "versionNonce": 39206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "browserToWeb", + "x": 900, + "y": 200, + "width": 80, + "height": 0, + "strokeColor": "#71717A", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29207, + "version": 1, + "versionNonce": 39207, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 80, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "ellipse", + "id": "web", + "x": 990, + "y": 125, + "width": 150, + "height": 150, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29208, + "version": 1, + "versionNonce": 39208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "webText", + "x": 990, + "y": 173, + "width": 150, + "height": 54, + "text": "WEB", + "originalText": "WEB", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 29209, + "version": 1, + "versionNonce": 39209, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-cdp-light.svg b/docs/cloud/images/browser-cdp-light.svg new file mode 100644 index 000000000..0f6ae71f4 --- /dev/null +++ b/docs/cloud/images/browser-cdp-light.svg @@ -0,0 +1,28 @@ + + Connect your code to a cloud browser with a CDP URL + Your code uses the CDP URL as a connection address to control a Browser Use cloud browser, which then accesses the web. + + + + + + + + + + + + + + + + + + + YOUR CODE + CDP URL + CLOUD + BROWSER + WEB + + diff --git a/docs/cloud/images/browser-profile-dark.excalidraw b/docs/cloud/images/browser-profile-dark.excalidraw new file mode 100644 index 000000000..053b03928 --- /dev/null +++ b/docs/cloud/images/browser-profile-dark.excalidraw @@ -0,0 +1,410 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "login", + "x": 60, + "y": 140, + "width": 270, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23101, + "version": 1, + "versionNonce": 33101, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "loginText", + "x": 60, + "y": 148, + "width": 270, + "height": 103, + "text": "LOG IN\nONCE", + "originalText": "LOG IN\nONCE", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23102, + "version": 1, + "versionNonce": 33102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "loginToProfile", + "x": 355, + "y": 200, + "width": 100, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23103, + "version": 1, + "versionNonce": 33103, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "profile", + "x": 475, + "y": 55, + "width": 300, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#111113", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23104, + "version": 1, + "versionNonce": 33104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "profileTitle", + "x": 500, + "y": 92, + "width": 250, + "height": 59, + "text": "PROFILE", + "originalText": "PROFILE", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23105, + "version": 1, + "versionNonce": 33105, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "text", + "id": "profileContents", + "x": 500, + "y": 177, + "width": 250, + "height": 92, + "text": "COOKIES\n+ LOGINS", + "originalText": "COOKIES\n+ LOGINS", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23106, + "version": 1, + "versionNonce": 33106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "profileToBrowser2", + "x": 795, + "y": 200, + "width": 95, + "height": -80, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23107, + "version": 1, + "versionNonce": 33107, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 40, + -25 + ], + [ + 60, + -65 + ], + [ + 95, + -80 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "profileToBrowser3", + "x": 795, + "y": 200, + "width": 95, + "height": 80, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23108, + "version": 1, + "versionNonce": 33108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 40, + 25 + ], + [ + 60, + 65 + ], + [ + 95, + 80 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "browser2", + "x": 910, + "y": 65, + "width": 230, + "height": 105, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23109, + "version": 1, + "versionNonce": 33109, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browser2Text", + "x": 910, + "y": 94, + "width": 230, + "height": 46, + "text": "BROWSER 2", + "originalText": "BROWSER 2", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23110, + "version": 1, + "versionNonce": 33110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "browser3", + "x": 910, + "y": 230, + "width": 230, + "height": 105, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23111, + "version": 1, + "versionNonce": 33111, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browser3Text", + "x": 910, + "y": 259, + "width": 230, + "height": 46, + "text": "BROWSER 3", + "originalText": "BROWSER 3", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23112, + "version": 1, + "versionNonce": 33112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-profile-dark.svg b/docs/cloud/images/browser-profile-dark.svg new file mode 100644 index 000000000..6f7c08bc0 --- /dev/null +++ b/docs/cloud/images/browser-profile-dark.svg @@ -0,0 +1,33 @@ + + One login reused across future browsers + A login is saved as a browser profile containing cookies and login state, then loaded into multiple future browsers. + + + + + + + + + + + + + + + + + + + + + + LOG IN + ONCE + PROFILE + COOKIES + + LOGINS + BROWSER 2 + BROWSER 3 + + diff --git a/docs/cloud/images/browser-profile-light.excalidraw b/docs/cloud/images/browser-profile-light.excalidraw new file mode 100644 index 000000000..ca5eb08d0 --- /dev/null +++ b/docs/cloud/images/browser-profile-light.excalidraw @@ -0,0 +1,410 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "login", + "x": 60, + "y": 140, + "width": 270, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23201, + "version": 1, + "versionNonce": 33201, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "loginText", + "x": 60, + "y": 148, + "width": 270, + "height": 103, + "text": "LOG IN\nONCE", + "originalText": "LOG IN\nONCE", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23202, + "version": 1, + "versionNonce": 33202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "loginToProfile", + "x": 355, + "y": 200, + "width": 100, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23203, + "version": 1, + "versionNonce": 33203, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "profile", + "x": 475, + "y": 55, + "width": 300, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23204, + "version": 1, + "versionNonce": 33204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "profileTitle", + "x": 500, + "y": 92, + "width": 250, + "height": 59, + "text": "PROFILE", + "originalText": "PROFILE", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23205, + "version": 1, + "versionNonce": 33205, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "text", + "id": "profileContents", + "x": 500, + "y": 177, + "width": 250, + "height": 92, + "text": "COOKIES\n+ LOGINS", + "originalText": "COOKIES\n+ LOGINS", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23206, + "version": 1, + "versionNonce": 33206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "profileToBrowser2", + "x": 795, + "y": 200, + "width": 95, + "height": -80, + "strokeColor": "#71717A", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23207, + "version": 1, + "versionNonce": 33207, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 40, + -25 + ], + [ + 60, + -65 + ], + [ + 95, + -80 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "profileToBrowser3", + "x": 795, + "y": 200, + "width": 95, + "height": 80, + "strokeColor": "#71717A", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23208, + "version": 1, + "versionNonce": 33208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 40, + 25 + ], + [ + 60, + 65 + ], + [ + 95, + 80 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "browser2", + "x": 910, + "y": 65, + "width": 230, + "height": 105, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23209, + "version": 1, + "versionNonce": 33209, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browser2Text", + "x": 910, + "y": 94, + "width": 230, + "height": 46, + "text": "BROWSER 2", + "originalText": "BROWSER 2", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23210, + "version": 1, + "versionNonce": 33210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "browser3", + "x": 910, + "y": 230, + "width": 230, + "height": 105, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23211, + "version": 1, + "versionNonce": 33211, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browser3Text", + "x": 910, + "y": 259, + "width": 230, + "height": 46, + "text": "BROWSER 3", + "originalText": "BROWSER 3", + "fontSize": 34, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 23212, + "version": 1, + "versionNonce": 33212, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-profile-light.svg b/docs/cloud/images/browser-profile-light.svg new file mode 100644 index 000000000..6d206b8e2 --- /dev/null +++ b/docs/cloud/images/browser-profile-light.svg @@ -0,0 +1,33 @@ + + One login reused across future browsers + A login is saved as a browser profile containing cookies and login state, then loaded into multiple future browsers. + + + + + + + + + + + + + + + + + + + + + + LOG IN + ONCE + PROFILE + COOKIES + + LOGINS + BROWSER 2 + BROWSER 3 + + diff --git a/docs/cloud/images/browser-proxy-dark.excalidraw b/docs/cloud/images/browser-proxy-dark.excalidraw new file mode 100644 index 000000000..2ef7de161 --- /dev/null +++ b/docs/cloud/images/browser-proxy-dark.excalidraw @@ -0,0 +1,262 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "browser", + "x": 70, + "y": 135, + "width": 270, + "height": 130, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17101, + "version": 1, + "versionNonce": 27101, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browserText", + "x": 70, + "y": 172, + "width": 270, + "height": 57, + "text": "BROWSER", + "originalText": "BROWSER", + "fontSize": 42, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17102, + "version": 1, + "versionNonce": 27102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "browserToProxy", + "x": 365, + "y": 200, + "width": 105, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17103, + "version": 1, + "versionNonce": 27103, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 105, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "ellipse", + "id": "proxy", + "x": 490, + "y": 95, + "width": 270, + "height": 210, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17104, + "version": 1, + "versionNonce": 27104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "proxyText", + "x": 505, + "y": 150, + "width": 240, + "height": 105, + "text": "RESIDENTIAL\nPROXY", + "originalText": "RESIDENTIAL\nPROXY", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17105, + "version": 1, + "versionNonce": 27105, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "proxyToWebsite", + "x": 780, + "y": 200, + "width": 105, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17106, + "version": 1, + "versionNonce": 27106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 105, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "website", + "x": 905, + "y": 135, + "width": 225, + "height": 130, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17107, + "version": 1, + "versionNonce": 27107, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "websiteText", + "x": 905, + "y": 173, + "width": 225, + "height": 54, + "text": "WEBSITE", + "originalText": "WEBSITE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17108, + "version": 1, + "versionNonce": 27108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-proxy-dark.svg b/docs/cloud/images/browser-proxy-dark.svg new file mode 100644 index 000000000..9409dd3c2 --- /dev/null +++ b/docs/cloud/images/browser-proxy-dark.svg @@ -0,0 +1,27 @@ + + Browser traffic routed through a residential proxy + A cloud browser sends traffic through a residential proxy before it reaches the target website. + + + + + + + + + + + + + + + + + + + BROWSER + RESIDENTIAL + PROXY + WEBSITE + + diff --git a/docs/cloud/images/browser-proxy-light.excalidraw b/docs/cloud/images/browser-proxy-light.excalidraw new file mode 100644 index 000000000..571f6c4f5 --- /dev/null +++ b/docs/cloud/images/browser-proxy-light.excalidraw @@ -0,0 +1,262 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "browser", + "x": 70, + "y": 135, + "width": 270, + "height": 130, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17201, + "version": 1, + "versionNonce": 27201, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "browserText", + "x": 70, + "y": 172, + "width": 270, + "height": 57, + "text": "BROWSER", + "originalText": "BROWSER", + "fontSize": 42, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17202, + "version": 1, + "versionNonce": 27202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "browserToProxy", + "x": 365, + "y": 200, + "width": 105, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17203, + "version": 1, + "versionNonce": 27203, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 105, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "ellipse", + "id": "proxy", + "x": 490, + "y": 95, + "width": 270, + "height": 210, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17204, + "version": 1, + "versionNonce": 27204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "proxyText", + "x": 505, + "y": 150, + "width": 240, + "height": 105, + "text": "RESIDENTIAL\nPROXY", + "originalText": "RESIDENTIAL\nPROXY", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17205, + "version": 1, + "versionNonce": 27205, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "proxyToWebsite", + "x": 780, + "y": 200, + "width": 105, + "height": 0, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17206, + "version": 1, + "versionNonce": 27206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 105, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "website", + "x": 905, + "y": 135, + "width": 225, + "height": 130, + "strokeColor": "#71717A", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17207, + "version": 1, + "versionNonce": 27207, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "websiteText", + "x": 905, + "y": 173, + "width": 225, + "height": 54, + "text": "WEBSITE", + "originalText": "WEBSITE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 17208, + "version": 1, + "versionNonce": 27208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/browser-proxy-light.svg b/docs/cloud/images/browser-proxy-light.svg new file mode 100644 index 000000000..b6f22c7fe --- /dev/null +++ b/docs/cloud/images/browser-proxy-light.svg @@ -0,0 +1,27 @@ + + Browser traffic routed through a residential proxy + A cloud browser sends traffic through a residential proxy before it reaches the target website. + + + + + + + + + + + + + + + + + + + BROWSER + RESIDENTIAL + PROXY + WEBSITE + + diff --git a/docs/cloud/images/v4-agent-overview-dark.excalidraw b/docs/cloud/images/v4-agent-overview-dark.excalidraw new file mode 100644 index 000000000..bd6428e4d --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-dark.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "task", + "x": 55, + "y": 160, + "width": 225, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10102, + "version": 1, + "versionNonce": 20102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "taskText", + "x": 55, + "y": 188, + "width": 225, + "height": 60, + "text": "TASK", + "originalText": "TASK", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10103, + "version": 1, + "versionNonce": 20103, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.4 + }, + { + "type": "arrow", + "id": "taskToRun", + "x": 298, + "y": 220, + "width": 59, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10104, + "version": 1, + "versionNonce": 20104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 59, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "session", + "x": 370, + "y": 55, + "width": 420, + "height": 310, + "strokeColor": "#71717A", + "backgroundColor": "#111113", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10105, + "version": 1, + "versionNonce": 20105, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sessionTitle", + "x": 408, + "y": 79, + "width": 300, + "height": 50, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10106, + "version": 1, + "versionNonce": 20106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "run", + "x": 450, + "y": 165, + "width": 260, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10107, + "version": 1, + "versionNonce": 20107, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "runText", + "x": 450, + "y": 194, + "width": 260, + "height": 60, + "text": "RUN", + "originalText": "RUN", + "fontSize": 46, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10108, + "version": 1, + "versionNonce": 20108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "runToWorkspace", + "x": 805, + "y": 220, + "width": 89, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10110, + "version": 1, + "versionNonce": 20110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 89, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 910, + "y": 135, + "width": 235, + "height": 185, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10111, + "version": 1, + "versionNonce": 20111, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceText", + "x": 910, + "y": 195, + "width": 235, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 36, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10112, + "version": 1, + "versionNonce": 20112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-agent-overview-dark.svg b/docs/cloud/images/v4-agent-overview-dark.svg new file mode 100644 index 000000000..ab32a12ed --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-dark.svg @@ -0,0 +1,29 @@ + + Task, session, run, and workspace relationship + A task starts a run inside a session. The run reads and writes a persistent workspace. + + + + + + + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + + diff --git a/docs/cloud/images/v4-agent-overview-light.excalidraw b/docs/cloud/images/v4-agent-overview-light.excalidraw new file mode 100644 index 000000000..65b2829eb --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-light.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "task", + "x": 55, + "y": 160, + "width": 225, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10102, + "version": 1, + "versionNonce": 20102, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "taskText", + "x": 55, + "y": 188, + "width": 225, + "height": 60, + "text": "TASK", + "originalText": "TASK", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10103, + "version": 1, + "versionNonce": 20103, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.4 + }, + { + "type": "arrow", + "id": "taskToRun", + "x": 298, + "y": 220, + "width": 59, + "height": 0, + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10104, + "version": 1, + "versionNonce": 20104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 59, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "session", + "x": 370, + "y": 55, + "width": 420, + "height": 310, + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10105, + "version": 1, + "versionNonce": 20105, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sessionTitle", + "x": 408, + "y": 79, + "width": 300, + "height": 50, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10106, + "version": 1, + "versionNonce": 20106, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "run", + "x": 450, + "y": 165, + "width": 260, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10107, + "version": 1, + "versionNonce": 20107, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "runText", + "x": 450, + "y": 194, + "width": 260, + "height": 60, + "text": "RUN", + "originalText": "RUN", + "fontSize": 46, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10108, + "version": 1, + "versionNonce": 20108, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "runToWorkspace", + "x": 805, + "y": 220, + "width": 89, + "height": 0, + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10110, + "version": 1, + "versionNonce": 20110, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 89, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 910, + "y": 135, + "width": 235, + "height": 185, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10111, + "version": 1, + "versionNonce": 20111, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceText", + "x": 910, + "y": 195, + "width": 235, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 36, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10112, + "version": 1, + "versionNonce": 20112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-agent-overview-light.svg b/docs/cloud/images/v4-agent-overview-light.svg new file mode 100644 index 000000000..4a5dc3479 --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-light.svg @@ -0,0 +1,29 @@ + + Task, session, run, and workspace relationship + A task starts a run inside a session. The run reads and writes a persistent workspace. + + + + + + + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + + diff --git a/docs/cloud/images/v4-scripts-dark.excalidraw b/docs/cloud/images/v4-scripts-dark.excalidraw new file mode 100644 index 000000000..e6cd7148d --- /dev/null +++ b/docs/cloud/images/v4-scripts-dark.excalidraw @@ -0,0 +1,370 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "firstRun", + "x": 55, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10401, + "version": 1, + "versionNonce": 20401, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "firstRunText", + "x": 55, + "y": 179, + "width": 225, + "height": 60, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10402, + "version": 1, + "versionNonce": 20402, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "saveArrow", + "x": 298, + "y": 210, + "width": 99, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10403, + "version": 1, + "versionNonce": 20403, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 99, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 415, + "y": 86, + "width": 370, + "height": 236, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10404, + "version": 1, + "versionNonce": 20404, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceText", + "x": 415, + "y": 100, + "width": 370, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10405, + "version": 1, + "versionNonce": 20405, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "script", + "x": 475, + "y": 156, + "width": 250, + "height": 108, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10406, + "version": 1, + "versionNonce": 20406, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "scriptText", + "x": 475, + "y": 181, + "width": 250, + "height": 60, + "text": "SCRIPT", + "originalText": "SCRIPT", + "fontSize": 46, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10407, + "version": 1, + "versionNonce": 20407, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "reuseArrow", + "x": 803, + "y": 210, + "width": 99, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10410, + "version": 1, + "versionNonce": 20410, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 99, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "laterRun", + "x": 920, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10411, + "version": 1, + "versionNonce": 20411, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "laterRunText", + "x": 920, + "y": 179, + "width": 225, + "height": 60, + "text": "RUN 2+", + "originalText": "RUN 2+", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10412, + "version": 1, + "versionNonce": 20412, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "repairArrow", + "x": 1035, + "y": 286, + "width": 472, + "height": 78, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10413, + "version": 1, + "versionNonce": 20413, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -20, + 78 + ], + [ + -325, + 56 + ], + [ + -472, + -8 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-scripts-dark.svg b/docs/cloud/images/v4-scripts-dark.svg new file mode 100644 index 000000000..2d3cb46ca --- /dev/null +++ b/docs/cloud/images/v4-scripts-dark.svg @@ -0,0 +1,31 @@ + + Save, reuse, and repair a browser script + The first run saves a script in a workspace. Later runs reuse and repair it. + + + + + + + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + SCRIPT + RUN 2+ + + diff --git a/docs/cloud/images/v4-scripts-light.excalidraw b/docs/cloud/images/v4-scripts-light.excalidraw new file mode 100644 index 000000000..2702bbdc5 --- /dev/null +++ b/docs/cloud/images/v4-scripts-light.excalidraw @@ -0,0 +1,370 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "firstRun", + "x": 55, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10401, + "version": 1, + "versionNonce": 20401, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "firstRunText", + "x": 55, + "y": 179, + "width": 225, + "height": 60, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10402, + "version": 1, + "versionNonce": 20402, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "saveArrow", + "x": 298, + "y": 210, + "width": 99, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10403, + "version": 1, + "versionNonce": 20403, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 99, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 415, + "y": 86, + "width": 370, + "height": 236, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10404, + "version": 1, + "versionNonce": 20404, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceText", + "x": 415, + "y": 100, + "width": 370, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10405, + "version": 1, + "versionNonce": 20405, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "script", + "x": 475, + "y": 156, + "width": 250, + "height": 108, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10406, + "version": 1, + "versionNonce": 20406, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "scriptText", + "x": 475, + "y": 181, + "width": 250, + "height": 60, + "text": "SCRIPT", + "originalText": "SCRIPT", + "fontSize": 46, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10407, + "version": 1, + "versionNonce": 20407, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "reuseArrow", + "x": 803, + "y": 210, + "width": 99, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10410, + "version": 1, + "versionNonce": 20410, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 99, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "laterRun", + "x": 920, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10411, + "version": 1, + "versionNonce": 20411, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "laterRunText", + "x": 920, + "y": 179, + "width": 225, + "height": 60, + "text": "RUN 2+", + "originalText": "RUN 2+", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10412, + "version": 1, + "versionNonce": 20412, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "repairArrow", + "x": 1035, + "y": 286, + "width": 472, + "height": 78, + "strokeColor": "#FE750E", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10413, + "version": 1, + "versionNonce": 20413, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -20, + 78 + ], + [ + -325, + 56 + ], + [ + -472, + -8 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-scripts-light.svg b/docs/cloud/images/v4-scripts-light.svg new file mode 100644 index 000000000..d34a56000 --- /dev/null +++ b/docs/cloud/images/v4-scripts-light.svg @@ -0,0 +1,31 @@ + + Save, reuse, and repair a browser script + The first run saves a script in a workspace. Later runs reuse and repair it. + + + + + + + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + SCRIPT + RUN 2+ + + diff --git a/docs/cloud/images/v4-sessions-dark.excalidraw b/docs/cloud/images/v4-sessions-dark.excalidraw new file mode 100644 index 000000000..8681d95b7 --- /dev/null +++ b/docs/cloud/images/v4-sessions-dark.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "session", + "x": 55, + "y": 55, + "width": 1090, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#111113", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10202, + "version": 1, + "versionNonce": 20202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sessionLabel", + "x": 92, + "y": 76, + "width": 300, + "height": 53, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 42, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10203, + "version": 1, + "versionNonce": 20203, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "run1", + "x": 105, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10204, + "version": 1, + "versionNonce": 20204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run1Text", + "x": 105, + "y": 184, + "width": 250, + "height": 60, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10205, + "version": 1, + "versionNonce": 20205, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow1", + "x": 375, + "y": 215, + "width": 100, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10206, + "version": 1, + "versionNonce": 20206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run2", + "x": 485, + "y": 155, + "width": 230, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10207, + "version": 1, + "versionNonce": 20207, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run2Text", + "x": 485, + "y": 184, + "width": 230, + "height": 60, + "text": "RUN 2", + "originalText": "RUN 2", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10208, + "version": 1, + "versionNonce": 20208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow2", + "x": 735, + "y": 215, + "width": 100, + "height": 0, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10209, + "version": 1, + "versionNonce": 20209, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run3", + "x": 845, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10210, + "version": 1, + "versionNonce": 20210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run3Text", + "x": 845, + "y": 184, + "width": 250, + "height": 60, + "text": "RUN 3", + "originalText": "RUN 3", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10211, + "version": 1, + "versionNonce": 20211, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-sessions-dark.svg b/docs/cloud/images/v4-sessions-dark.svg new file mode 100644 index 000000000..1f5e9aff3 --- /dev/null +++ b/docs/cloud/images/v4-sessions-dark.svg @@ -0,0 +1,28 @@ + + One session with multiple runs + One session ID contains three sequential runs. + + + + + + + + + + + + + + + + + + + + SESSION ID + RUN 1 + RUN 2 + RUN 3 + + diff --git a/docs/cloud/images/v4-sessions-light.excalidraw b/docs/cloud/images/v4-sessions-light.excalidraw new file mode 100644 index 000000000..b36615f2b --- /dev/null +++ b/docs/cloud/images/v4-sessions-light.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "session", + "x": 55, + "y": 55, + "width": 1090, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10202, + "version": 1, + "versionNonce": 20202, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "sessionLabel", + "x": 92, + "y": 76, + "width": 300, + "height": 53, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 42, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10203, + "version": 1, + "versionNonce": 20203, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "run1", + "x": 105, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10204, + "version": 1, + "versionNonce": 20204, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run1Text", + "x": 105, + "y": 184, + "width": 250, + "height": 60, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10205, + "version": 1, + "versionNonce": 20205, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow1", + "x": 375, + "y": 215, + "width": 100, + "height": 0, + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10206, + "version": 1, + "versionNonce": 20206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run2", + "x": 485, + "y": 155, + "width": 230, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10207, + "version": 1, + "versionNonce": 20207, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run2Text", + "x": 485, + "y": 184, + "width": 230, + "height": 60, + "text": "RUN 2", + "originalText": "RUN 2", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10208, + "version": 1, + "versionNonce": 20208, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow2", + "x": 735, + "y": 215, + "width": 100, + "height": 0, + "strokeColor": "#52525B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10209, + "version": 1, + "versionNonce": 20209, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run3", + "x": 845, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10210, + "version": 1, + "versionNonce": 20210, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "run3Text", + "x": 845, + "y": 184, + "width": 250, + "height": 60, + "text": "RUN 3", + "originalText": "RUN 3", + "fontSize": 44, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10211, + "version": 1, + "versionNonce": 20211, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-sessions-light.svg b/docs/cloud/images/v4-sessions-light.svg new file mode 100644 index 000000000..158c28391 --- /dev/null +++ b/docs/cloud/images/v4-sessions-light.svg @@ -0,0 +1,28 @@ + + One session with multiple runs + One session ID contains three sequential runs. + + + + + + + + + + + + + + + + + + + + SESSION ID + RUN 1 + RUN 2 + RUN 3 + + diff --git a/docs/cloud/images/v4-workspaces-dark.excalidraw b/docs/cloud/images/v4-workspaces-dark.excalidraw new file mode 100644 index 000000000..d80f52ad6 --- /dev/null +++ b/docs/cloud/images/v4-workspaces-dark.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "session1", + "x": 60, + "y": 82, + "width": 280, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10302, + "version": 1, + "versionNonce": 20302, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "session1Text", + "x": 60, + "y": 111, + "width": 280, + "height": 60, + "text": "SESSION A", + "originalText": "SESSION A", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10303, + "version": 1, + "versionNonce": 20303, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "session2", + "x": 60, + "y": 292, + "width": 280, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10304, + "version": 1, + "versionNonce": 20304, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "session2Text", + "x": 60, + "y": 321, + "width": 280, + "height": 60, + "text": "SESSION B", + "originalText": "SESSION B", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10305, + "version": 1, + "versionNonce": 20305, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow1", + "x": 355, + "y": 142, + "width": 112, + "height": 78, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10306, + "version": 1, + "versionNonce": 20306, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112, + 78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow2", + "x": 355, + "y": 352, + "width": 112, + "height": -78, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10307, + "version": 1, + "versionNonce": 20307, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112, + -78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 480, + "y": 88, + "width": 655, + "height": 332, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10308, + "version": 1, + "versionNonce": 20308, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceTitle", + "x": 480, + "y": 106, + "width": 655, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10309, + "version": 1, + "versionNonce": 20309, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "file1", + "x": 610, + "y": 188, + "width": 395, + "height": 145, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10310, + "version": 1, + "versionNonce": 20310, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "file1Text", + "x": 610, + "y": 229, + "width": 395, + "height": 65, + "text": "FILES", + "originalText": "FILES", + "fontSize": 48, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10311, + "version": 1, + "versionNonce": 20311, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#09090B", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-workspaces-dark.svg b/docs/cloud/images/v4-workspaces-dark.svg new file mode 100644 index 000000000..071f27afb --- /dev/null +++ b/docs/cloud/images/v4-workspaces-dark.svg @@ -0,0 +1,29 @@ + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. + + + + + + + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + FILES + + diff --git a/docs/cloud/images/v4-workspaces-light.excalidraw b/docs/cloud/images/v4-workspaces-light.excalidraw new file mode 100644 index 000000000..7f8810a9d --- /dev/null +++ b/docs/cloud/images/v4-workspaces-light.excalidraw @@ -0,0 +1,324 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "session1", + "x": 60, + "y": 82, + "width": 280, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10302, + "version": 1, + "versionNonce": 20302, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "session1Text", + "x": 60, + "y": 111, + "width": 280, + "height": 60, + "text": "SESSION A", + "originalText": "SESSION A", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10303, + "version": 1, + "versionNonce": 20303, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "rectangle", + "id": "session2", + "x": 60, + "y": 292, + "width": 280, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10304, + "version": 1, + "versionNonce": 20304, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "session2Text", + "x": 60, + "y": 321, + "width": 280, + "height": 60, + "text": "SESSION B", + "originalText": "SESSION B", + "fontSize": 38, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10305, + "version": 1, + "versionNonce": 20305, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "arrow", + "id": "arrow1", + "x": 355, + "y": 142, + "width": 112, + "height": 78, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10306, + "version": 1, + "versionNonce": 20306, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112, + 78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow2", + "x": 355, + "y": 352, + "width": 112, + "height": -78, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10307, + "version": 1, + "versionNonce": 20307, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 112, + -78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 480, + "y": 88, + "width": 655, + "height": 332, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10308, + "version": 1, + "versionNonce": 20308, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "workspaceTitle", + "x": 480, + "y": 106, + "width": 655, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10309, + "version": 1, + "versionNonce": 20309, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "file1", + "x": 610, + "y": 188, + "width": 395, + "height": 145, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10310, + "version": 1, + "versionNonce": 20310, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "file1Text", + "x": 610, + "y": 229, + "width": 395, + "height": 65, + "text": "FILES", + "originalText": "FILES", + "fontSize": 48, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10311, + "version": 1, + "versionNonce": 20311, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + } + ], + "appState": { + "viewBackgroundColor": "#FFFFFF", + "gridSize": null + }, + "files": {} +} diff --git a/docs/cloud/images/v4-workspaces-light.svg b/docs/cloud/images/v4-workspaces-light.svg new file mode 100644 index 000000000..93ee09b15 --- /dev/null +++ b/docs/cloud/images/v4-workspaces-light.svg @@ -0,0 +1,29 @@ + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. + + + + + + + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + FILES + + diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 8da85e616..e8c4118ad 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -5,7 +5,19 @@ Source: https://docs.browser-use.com/cloud/quickstart -## 1. Install +Give an agent a task and get the result. +Launch a cloud browser and connect to it from your code. + +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: + +```bash +export BROWSER_USE_API_KEY=your_key +``` + +## Install the SDK + +Skip this step if you use curl. ```bash Python pip install browser-use-sdk @@ -14,1763 +26,1246 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: - -```bash -export BROWSER_USE_API_KEY=your_key -``` - -## 2. Run your first task +## Run a hosted agent ```python Python -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) +from browser_use_sdk.v4 import BrowserUse -asyncio.run(main()) +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); -``` - -Want a full working app? Check out the [Chat UI example](https://docs.browser-use.com/cloud/tutorials/chat-ui). - -## Agent vs Browser - -| | **Agent** | **Browser** | -|---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | ✓ | ✓ | -| custom_proxy | ✓ | ✓ | -| profile_id | ✓ | ✓ | -| recording | ✓ | ✓ | -| workspace_id | ✓ | — | -| keep_alive | ✓ | — | -| screen size | — | ✓ | -| timeout | — | ✓ | - ---- - -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 - - -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 +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` - - -# 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 \ +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{}' -``` - -Request body, optional (include a user email/name if available): - -```json -{ - "email": "user@example.com", - "name": "User Name" -} + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -Response: - -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` +## Control a browser -### 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 +Launch a browser, connect to its CDP URL, then stop it: -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` +```python Python +from browser_use_sdk.v3 import BrowserUse -Request body: +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} +# When finished: +client.browsers.stop(browser.id) ``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; -Response: +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); -```json -{ - "api_key": "bu_..." -} +// When finished: +await client.browsers.stop(browser.id); ``` +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -Use the returned key for Browser Use Cloud API requests. - -For example, create a browser session: +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{}' + -d '{"action":"stop"}' ``` -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + Closing Playwright, Puppeteer, or CDP does not stop the browser. Use + `client.browsers.stop(browser.id)` or call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. -## Claim the account + Give your coding agent the compact API V4 context. -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: +# Prompt for Vibecoders +Source: https://docs.browser-use.com/cloud/vibecoding -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` -Response: +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. -```json -{ - "claim_url": "https://..." -} +``` +https://docs.browser-use.com/cloud/llms.txt ``` -The claim URL is valid for 1 hour. - -# Introduction +# Run a task Source: https://docs.browser-use.com/cloud/agent/quickstart -The SDK is a thin wrapper around the [API v3 Reference](https://docs.browser-use.com/cloud/api-reference). Every endpoint in the API reference is available as an SDK method — `client.sessions`, `client.browsers`, `client.profiles`, `client.workspaces`, and `client.billing`. +Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: -`client.run()` creates a session, polls every 2 seconds until completion (up to 4 hours), and returns the result. It accepts all parameters from the [Create Session](https://docs.browser-use.com/cloud/api-v3/sessions/create-session) endpoint. The result is a [Session object](https://docs.browser-use.com/cloud/api-v3/sessions/get-session) — use `result.output` for the agent's response. +```bash +export BROWSER_USE_API_KEY=your_key +``` ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -**What this agent can do:** -- **Data extraction** — scrape websites with thousands of listings -- **Form filling** — submit applications, fill out surveys, enter data -- **Multi-step workflows** — log in, navigate, click through flows, download files -- **Research** — search across multiple sites, compare results, summarize findings -- **Monitoring** — monitor a website and get notified if something changes -- **Testing** — test websites end-to-end with natural language instructions -- **Scheduling** — schedule tasks to run on a recurring basis -- **1,000+ integrations** — Gmail, Calendar, Notion, and more +Install the SDK with `pip install browser-use-sdk` or +`npm install browser-use-sdk`. Curl needs no installation. -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +Every new run implicitly creates a [session](https://docs.browser-use.com/cloud/agent/sessions) for its +conversation and live browser, plus a [workspace](https://docs.browser-use.com/cloud/agent/workspaces) for +persistent files. -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. +A task starts a run inside a session and the run reads and writes persistent workspace files +A task starts a run inside a session and the run reads and writes persistent workspace files +Give the compact context file to your coding agent. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` to select a model: +Pass one of these API strings as `model` when creating a run: + +| Model | API string | Input | Cache read | Output | BYOK | +| ----- | ---------- | ----: | ---------: | -----: | ---- | +| Claude Opus 5 | `claude-opus-5` | \$6.00 | \$0.60 | \$30.00 | Anthropic | +| Grok 4.5 | `grok-4.5` | \$2.40 | \$0.36 | \$7.20 | — | +| GPT-5.6 | `gpt-5.6` | \$6.00 | \$0.60 | \$36.00 | OpenAI | +| Gemini 3.5 Flash | `gemini-3.5-flash` | \$1.80 | \$0.18 | \$10.80 | Google | +| MiniMax M3 | `minimax-m3` | \$0.36 | \$0.072 | \$1.44 | — | -| Model | API String | Input (per 1M tokens) | Output (per 1M tokens) | -| ----- | ---------- | --------------------- | ---------------------- | -| Claude Sonnet 4.6 | `claude-sonnet-4.6` | \$3.60 | \$18.00 | -| Claude Opus 4.6 | `claude-opus-4.6` | \$6.00 | \$30.00 | -| GPT-5.4 mini | `gpt-5.4-mini` | \$0.90 | \$5.40 | +Token prices are USD per 1 million tokens. Browser sessions +(\$0.02/hour) and network traffic (\$5/GB managed proxy or \$0.20/GB +proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.com/pricing). - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **Grok 4.5** gives the best balance of price and accuracy. **MiniMax M3** is + the default and cheapest option; use **Claude Opus 5** when accuracy matters + most. + + New model strings can reach the API before the TypeScript SDK's generated + union. If TypeScript rejects a model listed above, call `POST /api/v4/runs` + directly for that model. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", +client = BrowserUse() +run = client.runs.create( + "Compare three project-management tools", + model="grok-4.5", ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const run = await client.runs.create({ + task: "Compare three project-management tools", + model: "grok-4.5", +}); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` +## Bring your own key + +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring +Your Own Key**. V4 uses it automatically for matching models; no request flag +is needed. You pay the provider directly, plus a 0.2× Browser Use orchestration +fee. Grok and MiniMax currently use Browser Use-managed keys. # Structured output Source: https://docs.browser-use.com/cloud/agent/structured-output -Pass a Pydantic model (Python) or Zod schema (TypeScript) — `result.output` is automatically validated and converted to the typed object. - - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel -class Post(BaseModel): -name: str -points: int -comments: int +client = BrowserUse() -class HNPosts(BaseModel): -posts: list[Post] +class Story(BaseModel): + title: str + points: int -client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const Post = z.object({ - name: z.string(), +const client = new BrowserUse(); + +const Story = z.object({ + title: z.string(), points: z.number(), - comments: z.number(), }); -const HNPosts = z.object({ - posts: z.array(Post), +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); - -const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { schema: HNPosts }, -); -for (const post of result.output.posts) { - console.log(`${post.name} (${post.points} pts, ${post.comments} comments)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` +V4 does not accept `output_schema` / `outputSchema`. Handle validation errors +and retry with a [session follow-up](https://docs.browser-use.com/cloud/agent/sessions) when needed. -# Follow-up tasks -Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks +# Sessions +Source: https://docs.browser-use.com/cloud/agent/sessions -When you pass a `session_id`, the session automatically stays alive between tasks. Each task runs a new agent that reuses the same browser — the agents don't share context, but the browser state (page, cookies, tabs) carries over. +A **session** holds the agent's conversation and can reuse its live browser. +One session ID can contain multiple runs. Every run creates a session implicitly +unless you pass an existing session ID. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser -client = AsyncBrowserUse() +Pass `session_id` / `sessionId` to continue: -# Create a session, then run tasks inside it -session = await client.sessions.create() +```python Python +first = client.runs.create("Open Hacker News") +client.runs.wait_for_completion(first.id) -result1 = await client.run( -"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, +follow_up = client.runs.create( + "Now summarize the top story", + session_id=first.session_id, ) - -await client.sessions.stop(session.id) +result = client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); - -// Create a session, then run tasks inside it -const session = await client.sessions.create(); - -const result1 = await client.run("Go to amazon.com, search for laptops, and open the first result", { - sessionId: session.id, -}); -const result2 = await client.run("Extract the customer reviews", { - sessionId: session.id, +const first = await client.runs.create({ + task: "Open Hacker News", + model: "grok-4.5", }); +await client.runs.waitForCompletion(first.id); -await client.sessions.stop(session.id); +const followUp = await client.runs.create({ + task: "Now summarize the top story", + model: "grok-4.5", + sessionId: first.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); ``` -`sessions.create()` returns a `live_url` you can embed to watch each task execute — see [Live preview](https://docs.browser-use.com/cloud/browser/live-preview). To stream messages as each task runs, use `client.run()` with `for await` — see [Live messages](https://docs.browser-use.com/cloud/agent/streaming). - - Sessions time out after 15 minutes of inactivity by default. The maximum session duration is 4 hours. +Omit the session ID for a new conversation. Pass only a [workspace +ID](https://docs.browser-use.com/cloud/agent/workspaces) when you want a fresh conversation that keeps the +same files. +# Workspaces & files +Source: https://docs.browser-use.com/cloud/agent/workspaces -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace -Stream messages as the agent works — reasoning, tool calls, browser actions, and results. Each message has `role`, `type`, `summary`, `data`, and `screenshot_url`. See [List session messages](https://docs.browser-use.com/cloud/api-v3/sessions/list-session-messages) for all fields. +## Upload and attach a file ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() - -run = client.run("Find the top story on Hacker News") -async for msg in run: -print(f"[{msg.role}] {msg.summary}") +client = BrowserUse() +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -print(run.result.output) +run = client.runs.create( + "Find everyone in the CSV who works at Google", + workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], +) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); +const workspace = await client.workspaces.create({ + name: "research", +}); +const uploaded = await client.workspaces.upload( + workspace.id, + "people.csv", +); -const run = client.run("Find the top story on Hacker News"); -for await (const msg of run) { - console.log(`[${msg.role}] ${msg.summary}`); -} - -console.log(run.result.output); +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + model: "grok-4.5", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` -``` -[user] Find the top story on Hacker News -[assistant] Navigating to https://news.ycombinator.com/ -[tool] Browser Navigate: Navigated -[assistant] Analyzing browser state -[tool] Browser Analyze State: The top story is "Coding Agents Could Make Free Software Matter Again" -[tool] Done Autonomous: The top story on Hacker News is "Coding Agents Could Make Free Software Matter Again" -``` +Attachments are run-scoped. Reusing a workspace does not reattach every upload. -## Cancel a running task +## Retrieve created files -Use `stop(strategy="task")` to cancel the current task without destroying the session. The session goes back to `idle` and can accept a new task. +Ask the agent to save its output, then list the workspace: ```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 -# Session is now idle — send a different task or close it +files = client.workspaces.files( + workspace.id, + include_urls=True, +) +for file in files.files: + print(file.path, file.url) ``` ```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; - } +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, +); +for (const file of files.files) { + console.log(file.path, file.url); } -// Session is now idle — send a different task or close it ``` - `run.result` is only available **after** the iterator finishes (all messages consumed or task completes). If you break early from `async for` / `for await`, the task may still be running — call `stop(strategy="task")` to cancel it before sending a follow-up. - -## Manual polling - -If you need full control over the polling loop (e.g. custom interval, filtering): +Download URLs expire after 60 seconds. See the [workspace API +reference](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for pagination and +limits. -```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +# Scripts +Source: https://docs.browser-use.com/cloud/agent/scripts -client = AsyncBrowserUse() -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 +Scripts turn a successful browser run into a reusable +[workspace](https://docs.browser-use.com/cloud/agent/workspaces) asset. The agent writes and tests the +helper once. Later runs execute it first and repair it only when the site +changes. -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary -print(s.output) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +Create one workspace for the workflow, then use these prompts with the same +`workspace_id` / `workspaceId`. The [run code](https://docs.browser-use.com/cloud/agent/quickstart) does not +change. -const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Find the top story on Hacker News", -}); +## First run -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; - } +```text +Get the top five Hacker News stories as JSON. - const s = await client.sessions.get(session.id); - if (["idle", "stopped", "error", "timed_out"].includes(s.status)) { -console.log(s.output); -break; - } - await new Promise((r) => setTimeout(r, 2000)); -} +Then reproduce exactly what you did as helper functions or a script. Test it, +save it in this workspace, and add a README with instructions for using it again. ``` -## Related - -- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — embed the browser alongside your message stream -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — chain multiple tasks in one session while streaming each +## Later runs +```text +Use the existing workspace script to get the top ten Hacker News stories. +Follow its README. Only fix and retest the script if it no longer works. +``` -# Workspaces & files -Source: https://docs.browser-use.com/cloud/agent/workspaces - +This is faster and cheaper for repeated workflows, but it still starts an agent +and uses tokens. The script is reusable and self-healing—not zero-LLM execution. -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +# Human in the loop +Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it -## Upload a file +Use a human checkpoint for approvals, authentication, payments, or review. +After a run stops, get its `live_view_url` from the `browser.ready` event: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" +) +run = client.runs.wait_for_completion(run.id) -# Upload -await client.workspaces.upload(workspace.id, "people.csv") +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" +) +print(ready.data["live_view_url"]) -# Agent can now read it -result = await client.run( -"Read people.csv and tell me who works at Google", -workspace_id=workspace.id, +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Upload -await client.workspaces.upload(workspace.id, "people.csv"); +const run = await client.runs.create({ + task: "Open the login page and stop for human review", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(run.id); -// Agent can now read it -const result = await client.run( - "Read people.csv and tell me who works at Google", - { workspaceId: workspace.id }, +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", ); -console.log(result.output); -``` - -You can upload multiple files at once: +console.log(ready?.data.live_view_url); -```python Python -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png") -``` -```typescript TypeScript -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png"); +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + model: "grok-4.5", + sessionId: run.sessionId, +}); ``` -## Download files +The same session preserves the conversation and workspace and reuses the live +browser while it is available. Treat live-view URLs as credentials. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +# Observability +Source: https://docs.browser-use.com/cloud/agent/observability -client = AsyncBrowserUse() -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, -) +Poll `runs.events()` with the previous cursor to receive only new events: + +```python Python +import time -# Download a single file -await client.workspaces.download(workspace.id, "posts.json", to="./posts.json") +after = None +while True: + page = client.runs.events(run.id, after=after) + for event in page.events: + print(event.type, event.data) + after = page.next_after if page.next_after is not None else after -# Or download everything -paths = await client.workspaces.download_all(workspace.id, to="./output") -for p in paths: -print(f"Downloaded: {p}") + status = client.runs.status(run.id).status.value + if status in {"completed", "failed", "cancelled"}: + break + time.sleep(1) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Agent creates a file -const result = await client.run( - "Go to Hacker News and save the top 3 posts as posts.json", - { workspaceId: workspace.id }, -); - -// Download a single file -await client.workspaces.download(workspace.id, "posts.json", { to: "./posts.json" }); +let after: number | undefined; +while (true) { + const page = await client.runs.events(run.id, { after }); + for (const event of page.events) { + console.log(event.type, event.data); + } + after = page.nextAfter ?? after; -// Or download everything -const paths = await client.workspaces.downloadAll(workspace.id, { to: "./output" }); -for (const p of paths) { - console.log(`Downloaded: ${p}`); + const { status } = await client.runs.status(run.id); + if (["completed", "failed", "cancelled"].includes(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); } ``` -## Manage workspaces +Events cover run lifecycle, model calls, browser readiness, tool activity, +artifacts, and completion. See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) +for the complete response shape. -```python Python -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) -await client.workspaces.delete(workspace_id) -``` -```typescript TypeScript -const workspace = await client.workspaces.get(workspaceId); -const updated = await client.workspaces.update(workspaceId, { name: "renamed" }); -const response = await client.workspaces.list(); -for (const w of response.items) { - console.log(w.id, w.name); -} -await client.workspaces.delete(workspaceId); -``` +# Browser quickstart +Source: https://docs.browser-use.com/cloud/browser/quickstart -## Organize with prefixes -Use `prefix` to organize files into directories within a workspace: +Every browser includes stealth, proxies, live preview, and recording. Its +**CDP URL** is a WebSocket endpoint for remotely controlling Chrome. -```python Python -# Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") +Your code using a CDP URL to connect to and control a cloud browser that accesses the web +Your code using a CDP URL to connect to and control a cloud browser that accesses the web -# List files in a subdirectory -files = await client.workspaces.files(workspace.id, prefix="reports/") -for f in files.files: -print(f.path, f.size) +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: -# Download only files from a subdirectory -await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") +```bash +export BROWSER_USE_API_KEY=your_key ``` -```typescript TypeScript -// Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", { prefix: "reports/" }); -// List files in a subdirectory -const files = await client.workspaces.files(workspace.id, { prefix: "reports/" }); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Download only files from a subdirectory -await client.workspaces.downloadAll(workspace.id, { to: "./output", prefix: "reports/" }); -``` +## Install the SDK -## List and delete files +Skip this step if you use curl. -```python Python -# List all files -files = await client.workspaces.files(workspace.id) -for f in files.files: -print(f.path, f.size) - -# Delete a single file -await client.workspaces.delete_file(workspace.id, path="old-report.pdf") - -# Check workspace storage usage -size = await client.workspaces.size(workspace.id) -print(f"Used: {size.used_bytes} bytes") +```bash Python +pip install browser-use-sdk ``` -```typescript TypeScript -// List all files -const files = await client.workspaces.files(workspace.id); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Delete a single file -await client.workspaces.deleteFile(workspace.id, "old-report.pdf"); - -// Check workspace storage usage -const size = await client.workspaces.size(workspace.id); -console.log(`Used: ${size.usedBytes} bytes`); +```bash TypeScript +npm install browser-use-sdk ``` -## Cloud dashboard - -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). - - Deleting a workspace permanently removes all its files. This cannot be undone. - - -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script - - -Deterministic rerun lets you run a browser task once with a full agent, then **re-execute the same task instantly** using a cached script — no LLM, up to 99% cheaper. - -## Quick start - -Use `@{{double brackets}}` around values that can change between runs. The first call runs the full agent. Every subsequent call with the same template uses the cached script. +## Launch a browser ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-scraper") +from browser_use_sdk.v3 import BrowserUse -# 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), -) +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -# 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), -) +# When finished: +client.browsers.stop(browser.id) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-scraper" }); - -// First call — agent explores, creates script (~$0.10, ~60s) -const result = await client.run( - "Get the top @{{5}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); -// Second call — cached script, different param ($0 LLM, ~5s) -const result2 = await client.run( - "Get the top @{{10}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); +// When finished: +await client.browsers.stop(browser.id); ``` +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -## How it works - -The brackets mark which parts are parameters: +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) +echo "$BROWSER_USE_CDP_URL" +# When finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' ``` -"Get prices from @{{example.com}} for @{{electronics}}" -``` - -- `@{{example.com}}` → parameter 1 -- `@{{electronics}}` → parameter 2 -The system strips the values to create a **template**: `"Get prices from @{{}} for @{{}}"`. -Template `"Get prices from @{{}} for @{{}}"` is hashed to a unique ID like `a7f3b2c1`. -The system checks the workspace for `scripts/a7f3b2c1.py`. -If no script exists, the full agent runs your task. After completing it, the agent saves a standalone Python script that reproduces the result deterministically — no AI needed. -If the script exists, it runs directly with the new parameter values. No agent, no LLM. Just the script in a sandbox with browser and proxy. + `browser.close()`, disconnecting CDP, or `client.close()` does not stop the + managed browser. Use `client.browsers.stop(browser.id)` or call + `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. -## Auto-detection + +Use the CDP URL with Playwright or Puppeteer. +Configure proxies, screen size, recording, and timeout. -Caching activates **automatically** when both conditions are met: -- The task contains `@{{` and `}}` -- A `workspace_id` is provided - -No extra flags needed. You can override with `cache_script`: +# Stealth +Source: https://docs.browser-use.com/cloud/browser/stealth -| Value | Behavior | -|-------|----------| -| `None` (default) | Auto-detect from `@{{brackets}}` + workspace | -| `True` | Force-enable, even without brackets | -| `False` | Force-disable, even if brackets are present | -## Examples +See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). -### Parameterized scraping +## What's included -Run once, then loop over different keywords at $0 LLM each: +Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. -```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), -) +- **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. -# 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}") -``` -```typescript TypeScript -// Agent figures out how to scrape intro.co on first call -let result = await client.run( - "Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", - { workspaceId: workspace.id }, -); +## Residential proxies -// 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 }, - ); - console.log(`${keyword}: ${result.output}`); -} -``` +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. -### No parameters — cache the exact task +# Proxies +Source: https://docs.browser-use.com/cloud/browser/proxies -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), -) +A US residential proxy is enabled by default. The browser's traffic passes +through that residential IP before reaching the website, so the website sees +the proxy's location—not your server's. -# Same task again — cached -result2 = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), -) -``` -```typescript TypeScript -let result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); +A cloud browser routing its traffic through a residential proxy before reaching a website +A cloud browser routing its traffic through a residential proxy before reaching a website -// Same task again — cached -result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); -``` +Set `browser_settings` / `browserSettings` when you create a V4 run to choose +another country: -### Multiple parameters + The current TypeScript SDK type requires `proxyCountryCode` whenever + `browserSettings` is present. Use `"us"` to keep the default, or `null` to + disable the managed proxy. ```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), -) +from browser_use_sdk.v4 import BrowserUse -# 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), +client = BrowserUse() +run = client.runs.create( + "Get the iPhone 16 price on amazon.de", + browser_settings={"proxyCountryCode": "de"}, ) ``` ```typescript TypeScript -let result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", - { workspaceId: workspace.id }, -); +import { BrowserUse } from "browser-use-sdk/v4"; -// Different countries — cached -result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", - { workspaceId: workspace.id }, -); +const client = new BrowserUse(); +const run = await client.runs.create({ + task: "Get the iPhone 16 price on amazon.de", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "de" }, +}); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Get the iPhone 16 price on amazon.de", + "browserSettings":{"proxyCountryCode":"de"}}' ``` -### Force enable / disable +## Disable proxies -```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, -) +Pass `null` for QA or internal sites that do not need a residential proxy: -# Force-disable even with brackets -result = await client.run( -"Explain what @{{templates}} means in Jinja", -workspace_id=str(workspace.id), -cache_script=False, +```python Python +run = client.runs.create( + "Test my staging site", + browser_settings={"proxyCountryCode": None}, ) ``` ```typescript TypeScript -// Force-enable without brackets -let result = await client.run( - "Get the top stories from Hacker News", - { workspaceId: workspace.id, cacheScript: true }, -); - -// Force-disable even with brackets -result = await client.run( - "Explain what @{{templates}} means in Jinja", - { workspaceId: workspace.id, cacheScript: false }, -); +const run = await client.runs.create({ + task: "Test my staging site", + model: "grok-4.5", + browserSettings: { proxyCountryCode: null }, +}); ``` -## Inspecting cached scripts +## Custom proxy -You can download and inspect the scripts the agent created: +Custom HTTP and SOCKS5 proxies are available on paid plans: ```python Python -files = await client.workspaces.files(workspace.id, prefix="scripts/") -for f in files.files: -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") +run = client.runs.create( + "Check the account dashboard", + browser_settings={ + "customProxy": { + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + "ignoreCertErrors": False, + } + }, +) ``` ```typescript TypeScript -const files = await client.workspaces.files(workspace.id, { prefix: "scripts/" }); -for (const f of files.files) { - console.log(`${f.path} (${f.size} bytes)`); -} +const run = await client.runs.create({ + task: "Check the account dashboard", + model: "grok-4.5", + browserSettings: { + proxyCountryCode: "us", + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + ignoreCertErrors: false, + }, + }, +}); ``` -## Auto-healing - -Cached scripts can break when a website changes its layout, adds new elements, or alters its structure. Auto-healing detects these failures and automatically regenerates the script. +A custom proxy overrides `proxyCountryCode` and must be passed again when a +follow-up provisions a new browser. See the [Create run +reference](https://docs.browser-use.com/cloud/api-v4/runs/create-run) for the complete settings object. -### How it works - -When a cached script runs, the system validates its output: +# Live preview & recording +Source: https://docs.browser-use.com/cloud/browser/live-preview -1. **Fast checks** (no LLM) — detects empty results, error fields in JSON, or exception keywords in output. -2. **LLM judge** — if fast checks pass, a lightweight model validates whether the output looks correct for the original task. -3. **Heal** — if validation fails, the full agent re-runs the task and saves an updated script. -Auto-healing is **limited to 1 attempt per run** to prevent runaway costs. If the healed script also fails, the output is returned as-is. +The `browser.ready` event contains the live browser URL: -### Cost impact +```python Python +from browser_use_sdk.v4 import BrowserUse -| Scenario | LLM cost | -|----------|----------| -| Cached script succeeds | **$0** | -| Cached script fails, auto-heals | ~$0.05–1.00 (one full agent run) | -| Healed script also fails | Same as above (returns best-effort output) | +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) -Auto-healing is enabled by default for all cached scripts. No configuration needed. +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" +) +print(ready.data["live_view_url"]) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; -## Cost comparison +const client = new BrowserUse(); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(run.id); -| | LLM cost | Browser + proxy | Time | -|---|---|---|---| -| First call (agent) | ~$0.05–1.00 | Yes | ~30–120s | -| Cached calls | **$0** | Yes | ~3–10s | +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", +); +console.log(ready?.data.live_view_url); +``` -The browser and proxy still run for cached calls (the script may need them), so there is a small infrastructure cost per execution. LLM cost drops to zero. +Poll [run events](https://docs.browser-use.com/cloud/agent/observability) if you need the URL as soon as +the browser starts. +## Embed the live browser -# Human in the loop -Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop +```html + +``` +The URL is hosted on `live.browser-use.com`. Add that origin to your +Content Security Policy's `frame-src` directive when needed. Treat the URL as +a credential: anyone with it can interact with the active browser. -## Use cases -- Human enters payment info or approves a transaction, agent handles the rest -- Human navigates a complex auth flow, then hands back to agent -- Human reviews what the agent did before the agent continues +## Recording - Sessions time out after 15 minutes of inactivity. The maximum session duration is 4 hours. If the human needs more time, send a lightweight follow-up task (e.g. "wait") to reset the inactivity timer. +Enable recording when the run creates its browser: -## Flow +```python Python +run = client.runs.create( + "Test the checkout flow", + browser_settings={"record": True}, +) +``` +```typescript TypeScript +const run = await client.runs.create({ + task: "Test the checkout flow", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, +}); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Test checkout","browserSettings":{"record":true}}' +``` -1. Create a session — it stays alive automatically when you pass `session_id` to `run()` -2. Run an agent task -3. Human interacts with the live browser -4. Send a new follow-up task +The MP4 becomes available in the Dashboard after the browser stops. API runs +default to recording off, and Zero Data Retention projects never record. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +# Playwright, Puppeteer, Selenium +Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium -client = AsyncBrowserUse() -# 1. Create a session -session = await client.sessions.create() -print(f"Live view: {session.live_url}") +Every browser 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. -# 2. Agent does the first part -result = await client.run( -"Go to amazon.com and search for noise cancelling headphones", -session_id=session.id, -) -print(result.output) + This page is for direct browser control. To give an AI agent a goal instead, + [create an API V4 run](https://docs.browser-use.com/cloud/agent/quickstart). -# 3. Human opens live_url and picks a product -input("Press Enter after you've selected a product in the live view...") +## 1. Create a browser -# 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, -) -print(result.output) +```bash +session=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -# Clean up -await client.sessions.stop(session.id) +export BROWSER_SESSION_ID=$(echo "$session" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$session" | jq -r .cdpUrl) ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; -const client = new BrowserUse(); +## 2. Connect over CDP -// 1. Create a session -const session = await client.sessions.create(); -console.log(`Live view: ${session.liveUrl}`); +### Playwright -// 2. Agent does the first part -const searchResult = await client.run( - "Go to amazon.com and search for noise cancelling headphones", - { sessionId: session.id }, -); -console.log(searchResult.output); +```python Python +import os +from playwright.sync_api import sync_playwright -// 3. Human opens liveUrl and picks a product -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => - rl.question("Press Enter after you've selected a product in the live view...", resolve), -); -rl.close(); +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(os.environ["BROWSER_USE_CDP_URL"]) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) +``` +```typescript TypeScript +import { chromium } from "playwright"; -// 4. Agent continues where the human left off -const result = await client.run( - "Get the details of the selected product — name, price, and rating", - { sessionId: session.id }, +const browser = await chromium.connectOverCDP( + process.env.BROWSER_USE_CDP_URL!, ); -console.log(result.output); - -// Clean up -await client.sessions.stop(session.id); +const page = browser.contexts()[0].pages()[0]; +await page.goto("https://example.com"); +console.log(await page.title()); ``` +### Puppeteer +```typescript +import puppeteer from "puppeteer-core"; -# Introduction Stealth -Source: https://docs.browser-use.com/cloud/browser/stealth +const browser = await puppeteer.connect({ + browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!, +}); +const [page] = await browser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +``` +### Selenium -See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. -## What's included +## 3. Stop the browser -Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. +```bash +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` -- **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. + `browser.close()` and disconnecting CDP do not stop the managed browser. + Call `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. -## Residential proxies +See [Create browser session](https://docs.browser-use.com/cloud/api-v4/browsers/create-browser-session) and +[Update browser session](https://docs.browser-use.com/cloud/api-v4/browsers/update-browser-session) for +every browser setting and response field. -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. +# Profiles +Source: https://docs.browser-use.com/cloud/guides/authentication -# Proxies -Source: https://docs.browser-use.com/cloud/browser/proxies +A profile persists cookies, local storage, and login state across browsers. +Log in once, save the profile, then reuse it to start future browsers already +logged in. +One login saved as a profile and reused by multiple future browsers +One login saved as a profile and reused by multiple future browsers -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. +Create or select one under [Dashboard → Profiles](https://cloud.browser-use.com/settings?tab=profiles), +then pass its ID in V4 browser settings: ```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 +from browser_use_sdk.v4 import BrowserUse -# With an agent: -# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +client = BrowserUse() +run = client.runs.create( + "Open my account dashboard and summarize it", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) +result = client.runs.wait_for_completion(run.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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" }); +const run = await client.runs.create({ + task: "Open my account dashboard and summarize it", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Summarize my account dashboard", + "browserSettings":{"profileId":"YOUR_PROFILE_ID"}}' ``` -## Disable proxies +Use one profile per end user. Follow-ups in the same [session](https://docs.browser-use.com/cloud/agent/sessions) +reuse the live browser; later sessions can load the same profile again. -If your use case does not need proxies, for example QA testing. +For the fastest setup, [sync an existing local login](https://docs.browser-use.com/cloud/guides/profile-sync). -```python Python -browser = await client.browsers.create(proxy_country_code=None) +# Sync local and cloud cookies +Source: https://docs.browser-use.com/cloud/guides/profile-sync -# 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 }); -``` +Run the profile sync helper: -## Custom proxy +```bash +export BROWSER_USE_API_KEY=your_key +curl -fsSL https://browser-use.com/profile.sh | sh +``` -Bring your own proxy server (HTTP or SOCKS5). +Choose the accounts to sync, then use the returned profile ID: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -browser = await client.browsers.create( -custom_proxy={ - "host": "proxy.example.com", - "port": 8080, - "username": "user", - "password": "pass", -}, +client = BrowserUse() +run = client.runs.create( + "Check my LinkedIn messages", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, ) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const browser = await client.browsers.create({ - customProxy: { -host: "proxy.example.com", -port: 8080, -username: "user", -password: "pass", +const run = await client.runs.create({ + task: "Check my LinkedIn messages", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", }, }); ``` +The profile supplies cookies and local storage without putting credentials in +the prompt. Re-sync when the site's login expires. -# Live preview & recording -Source: https://docs.browser-use.com/cloud/browser/live-preview +# 2FA +Source: https://docs.browser-use.com/cloud/guides/2fa - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +The most reliable options are a saved profile or a human checkpoint. -`liveUrl` is returned on session creation. +## Reuse a logged-in profile -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +[Sync your local login](https://docs.browser-use.com/cloud/guides/profile-sync), then load that profile in +the run: -client = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +```python Python +run = client.runs.create( + "Download my latest invoice", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) ``` ```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", +const run = await client.runs.create({ + task: "Download my latest invoice", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); -console.log(session.liveUrl); ``` -`liveUrl` is also returned when creating a standalone browser session: +This avoids another 2FA challenge while the site's cookies remain valid. + +## Let a human take over + +Ask the first run to stop at the 2FA screen, get its `live_view_url` from the +[`browser.ready` event](https://docs.browser-use.com/cloud/agent/human-in-the-loop), and have the user enter +the code. Then continue with the same session: ```python Python -browser = await client.browsers.create() -print(browser.live_url) +first = client.runs.create( + "Open the login page and stop at the 2FA prompt", +) +client.runs.wait_for_completion(first.id) + +next_run = client.runs.create( + "Continue after login and download the invoice", + session_id=first.session_id, +) ``` ```typescript TypeScript -const browser = await client.browsers.create(); -console.log(browser.liveUrl); +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(first.id); + +const nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + model: "grok-4.5", + sessionId: first.sessionId, +}); ``` -## Embed live browser into your app +See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for retrieving and +embedding the live browser URL. Never put passwords or TOTP secrets directly +in a prompt. -Useful for human interaction or to see live what's happening. +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code -```html - + +[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. + +## Setup + +**1. Install the CLI** + +```bash +uv tool install browser-use ``` -The live URL is hosted on `live.browser-use.com`. If your app sets a Content Security Policy, add it to your `frame-src` directive: +**2. Verify the installation** +```bash +browser-use doctor ``` -Content-Security-Policy: frame-src 'self' https://live.browser-use.com; + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install ``` -For responsive sizing, use CSS instead of fixed dimensions: +**4. Authenticate for cloud browsers** -```html - +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login ``` -## Customize +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. -Append query parameters to the `liveUrl`: +**5. Use it** -| Parameter | Values | Description | -|-----------|--------|-------------| -| `theme` | `light`, `dark` (default) | Light or dark mode | -| `ui` | `false` | Hide the browser chrome (URL bar, tabs) | +Claude Code uses its bash tool to run CLI commands directly: ``` -https://live.browser-use.com?wss=...&theme=light -https://live.browser-use.com?wss=...&ui=false +> Use browser-use to open github.com/trending and summarize the top repos ``` -## Recording +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). - `waitForRecording` / `wait_for_recording` requires the **v3 SDK** (`from browser_use_sdk.v3 import AsyncBrowserUse` / `import { BrowserUse } from "browser-use-sdk/v3"`). +## Agent Self-Registration -Enable recording to get an MP4 video of the browser session. Only available when the agent actually opens a browser — tasks answered without browsing produce no recording. If you run multiple tasks in the same session (with `keep_alive`), you may get multiple recordings. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -result = await client.run( -"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 -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const result = await client.run("Check how many GitHub stars browser-use has", { - enableRecording: true, -}); - -// Waits up to 15s for recording to be ready. Returns [] if no browser was opened. -const urls = await client.sessions.waitForRecording(result.id); -for (const url of urls) { - console.log(url); // presigned MP4 download URL -} -``` - -For standalone browser sessions, pass `enable_recording` when creating the browser and retrieve the URL after stopping it: - -```python Python -browser = await client.browsers.create(enable_recording=True) -# ... use the browser via CDP ... -stopped = await client.browsers.stop(browser.id) -print(stopped.recording_url) # presigned MP4 download URL -``` -```typescript TypeScript -const browser = await client.browsers.create({ enableRecording: true }); -// ... use the browser via CDP ... -const stopped = await client.browsers.stop(browser.id); -console.log(stopped.recordingUrl); // presigned MP4 download URL -``` - - Recording URLs are presigned and **expire after 1 hour**. Download or serve the recording promptly. If you need to access it later, save the MP4 to your own storage. - -## Related - -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming) — stream the agent's messages alongside the live browser view -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — chain tasks in one session while watching live - - - -# Playwright, Puppeteer, Selenium -Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium - - -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. - -## 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 is automatically stopped when the WebSocket disconnects -``` -```typescript TypeScript -import { chromium } from "playwright"; - -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await chromium.connectOverCDP(WSS_URL); -const page = browser.contexts()[0].pages()[0]; -await page.goto("https://example.com"); -console.log(await page.title()); -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 - -| 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, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse -from playwright.async_api import async_playwright - -client = AsyncBrowserUse() -browser = await client.browsers.create() -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() - -await client.browsers.stop(browser.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { chromium } from "playwright"; - -const client = new BrowserUse(); -const browser = await client.browsers.create(); -console.log(browser.cdpUrl); // https://uuid.cdpN.browser-use.com -console.log(browser.liveUrl); // https://live.browser-use.com?wss=... - -const pwBrowser = await chromium.connectOverCDP(browser.cdpUrl); -const page = pwBrowser.contexts()[0].pages()[0]; -await page.goto("https://example.com"); -console.log(await page.title()); -await pwBrowser.close(); - -await client.browsers.stop(browser.id); -``` - -### Puppeteer - -```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 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); -``` - - Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. - - -# Profiles -Source: https://docs.browser-use.com/cloud/guides/authentication - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -profile = await client.profiles.create(name="user-id-1") -# or search existing -# profile = (await client.profiles.list(query="user-id-1")).items[0] -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Check browser-use github stars", session_id=session.id) -print(result.output) - -# Always stop the session to persist profile state -await client.sessions.stop(session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const profile = await client.profiles.create({ name: "user-id-1" }); -// or search existing -// const profile = (await client.profiles.list({ query: "user-id-1" })).items[0]; -const session = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Check browser-use github stars", { - sessionId: session.id, -}); -console.log(result.output); - -// Always stop the session to persist profile state -await client.sessions.stop(session.id); -``` - -View your profile IDs at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=profiles). - -## Manage profiles - -```python Python -# Create -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) - -# Search by name -response = await client.profiles.list(query="user-id-1") -profile = response.items[0] # first match - -# Get one by ID -profile = await client.profiles.get(profile_id) - -# Update -await client.profiles.update(profile_id, name="renamed") - -# Delete -await client.profiles.delete(profile_id) -``` -```typescript TypeScript -// Create -const profile = await client.profiles.create({ name: "work-account" }); - -// List all -const response = await client.profiles.list(); -for (const p of response.items) { - console.log(p.id, p.name); -} - -// Search by name -const results = await client.profiles.list({ query: "user-id-1" }); -const found = results.items[0]; // first match - -// Get one by ID -const fetched = await client.profiles.get(profileId); - -// Update -await client.profiles.update(profileId, { name: "renamed" }); - -// Delete -await client.profiles.delete(profileId); -``` - -## Usage patterns - -- **Per-user profiles:** Create one profile per end-user. Query by name to get the profile ID, or store a mapping between your users and their profile IDs in your database. - - 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. - - -# Sync local and cloud cookies -Source: https://docs.browser-use.com/cloud/guides/profile-sync +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. +Install the Browser Use CLI and skill: ```bash -export BROWSER_USE_API_KEY=your_key && curl -fsSL https://browser-use.com/profile.sh | sh -``` - -This opens a browser where you select which accounts to sync. After syncing, you receive a `profile_id` to use in your tasks. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -session = await client.sessions.create(profile_id="your_synced_profile_id") -result = await client.run("Check my LinkedIn messages", session_id=session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const session = await client.sessions.create({ profileId: "your_synced_profile_id" }); -const result = await client.run("Check my LinkedIn messages", { - sessionId: session.id, -}); -``` - - -# 2FA -Source: https://docs.browser-use.com/cloud/guides/2fa - - -Sites with 2FA block automated logins. Here are four approaches — pick the one that fits your setup. - -| Approach | Best for | Complexity | -|---|---|---| -| [Profiles (login once)](#1-profiles--login-once-reuse-cookies) | Sites with long-lived cookies | Lowest | -| [Human in the loop](#2-human-in-the-loop) | One-off tasks, complex auth flows | Low | -| [Agent Mail](#3-agent-mail) | Email-based 2FA, end-client automation | Medium | -| [TOTP secret in prompt](#4-totp-secret-in-prompt) | Authenticator app 2FA (Google Authenticator, Authy) | Medium | - ---- - -## 1. Profiles — login once, reuse cookies - -Login manually once (or let the agent do it), then save the browser state as a profile. Future sessions reuse the cookies — no 2FA prompt as long as the cookies are valid. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# Create a profile and a session -profile = await client.profiles.create(name="my-account") -session = await client.sessions.create(profile_id=profile.id) -print(f"Live view: {session.live_url}") - -# Option A: human logs in via live view -input("Log in and complete 2FA in the live view, then press Enter...") - -# Option B: let the agent log in -# await client.run("Log in to example.com with user@example.com / password123", session_id=session.id) - -# Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id) - -# Next time: reuse the profile, no 2FA needed -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Go to example.com/dashboard and get my balance", 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(); - -// Create a profile and a session -const profile = await client.profiles.create({ name: "my-account" }); -const session = await client.sessions.create({ profileId: profile.id }); -console.log(`Live view: ${session.liveUrl}`); - -// Option A: human logs in via live view -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => rl.question("Log in and complete 2FA in the live view, then press Enter...", resolve)); -rl.close(); - -// Option B: let the agent log in -// await client.run("Log in to example.com with user@example.com / password123", { sessionId: session.id }); - -// Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id); - -// Next time: reuse the profile, no 2FA needed -const newSession = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Go to example.com/dashboard and get my balance", { sessionId: newSession.id }); -console.log(result.output); -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. - ---- - -## 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) +uv tool install browser-use +browser-use skill install ``` -```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}`); +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: -// 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); +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. +### 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. -## 3. Agent Mail +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents -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. +[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. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() +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. -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"; +## 1. Create an environment -const client = new BrowserUse(); +Pre-install the CLI so it's ready at session start (no runtime install). -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); +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true ``` -### 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. +## 2. Create a credential vault -### Connect external email via Composio +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). -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. +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | ---- - -## 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`. +## 3. Create the agent -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() +Tell it to use the CLI in cloud mode. -# 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) +```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 ``` -```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"; +## 4. Start a session and send a task -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() +The Console only observes; kick the agent off with a `user.message` event. - Enter the generated code.`, -); -console.log(result.output); +```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."}]}]}' ``` -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? +## 5. Watch it run -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. +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**. + 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 Source: https://docs.browser-use.com/cloud/tutorials/integrations/openclaw @@ -1798,733 +1293,314 @@ 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", - }, -}, - }, -} -``` - -Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: - -- `timeout` — session duration in minutes (max 240) -- `profileId` — load a saved browser profile with persistent cookies and localStorage -- `proxyCountryCode` — route traffic through a specific country (e.g. `us`, `de`, `jp`) - -**3. Use it** - -OpenClaw's browser commands now run against a Browser Use cloud browser: - -```bash -openclaw browser --browser-profile browser-use open https://example.com -openclaw browser --browser-profile browser-use snapshot -openclaw browser --browser-profile browser-use screenshot -``` - -If you set `defaultProfile` to `"browser-use"` in the config (as shown above), you can drop the `--browser-profile` flag: - -```bash -openclaw browser open https://example.com -openclaw browser snapshot -openclaw browser screenshot -``` - -## Option 2: Browser Use CLI - -The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser automation through a SKILL.md file. The agent reads the skill and learns to use the CLI commands directly. It's available on [skills.sh](https://skills.sh/browser-use/browser-use/browser-use) and [ClawHub](https://clawhub.ai/ShawnPana/browser-use). - -### Setup - -**1. Install the CLI** - -```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash -``` - -**2. Verify the installation** - -```bash -browser-use doctor -``` - -**3. Set up the agent** - -Paste this setup prompt into your OpenClaw agent: - -```text -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). - - -# 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", "") + browser: { + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, + }, +} +``` -# 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") +Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: -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() +- `timeout` — session duration in minutes (max 240) +- `profileId` — load a saved browser profile with persistent cookies and localStorage +- `proxyCountryCode` — route traffic through a specific country (e.g. `us`, `de`, `jp`) -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +**3. Use it** -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}") +OpenClaw's browser commands now run against a Browser Use cloud browser: -return {"status": "ok"} +```bash +openclaw browser --browser-profile browser-use open https://example.com +openclaw browser --browser-profile browser-use snapshot +openclaw browser --browser-profile browser-use screenshot ``` - 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. - +If you set `defaultProfile` to `"browser-use"` in the config (as shown above), you can drop the `--browser-profile` flag: -# n8n -Source: https://docs.browser-use.com/cloud/tutorials/integrations/n8n +```bash +openclaw browser open https://example.com +openclaw browser snapshot +openclaw browser screenshot +``` +## Option 2: Browser Use CLI -Browser Use works with [n8n](https://n8n.io) as a standard HTTP integration — no custom nodes needed. +The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser automation through a SKILL.md file. The agent reads the skill and learns to use the CLI commands directly. It's available on [skills.sh](https://skills.sh/browser-use/browser-use/browser-use) and [ClawHub](https://clawhub.ai/ShawnPana/browser-use). -## 1. Create a credential +### Setup -In n8n, go to **Credentials → Add Credential → Header Auth** and set: +**1. Install the CLI** -| Field | Value | -|-------|-------| -| Name | `Authorization` | -| Value | `Bearer YOUR_API_KEY` | +```bash +uv tool install browser-use +``` -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). +**2. Verify the installation** -## 2. Start a session +```bash +browser-use doctor +``` -Add an **HTTP Request** node: +**3. Set up the agent** -| Setting | Value | -|---------|-------| -| Method | `POST` | -| URL | `https://api.browser-use.com/api/v3/sessions` | -| Authentication | Header Auth (from step 1) | -| Body Type | JSON | +Paste this setup prompt into your OpenClaw agent: -Body: -```json -{ - "task": "Find the top 3 trending repos on GitHub today" -} +```text +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. ``` -The response includes a `session_id` you'll use to poll for results. - -## 3. Poll for completion - -Add a second **HTTP Request** node in a loop: +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. -| Setting | Value | -|---------|-------| -| Method | `GET` | -| URL | `https://api.browser-use.com/api/v3/sessions/{{ $json.id }}` | -| Authentication | Header Auth (from step 1) | +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -Check the `status` field. The session is done when status is `idle`, `stopped`, `error`, or `timed_out`. Use an **If** node to loop back with a **Wait** node (5–10 seconds) until complete. +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -The final response contains `output` with the agent's result. -## Event-driven alternative +[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. -Instead of polling, use [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks) to receive a callback when the session completes. Configure your webhook endpoint in the [dashboard](https://cloud.browser-use.com/settings?tab=webhooks), then add a **Webhook** trigger node in n8n to receive `agent.task.status_update` events when sessions finish. +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. - This pattern works with any workflow tool that supports HTTP requests — Make, Zapier, Pipedream, or custom orchestrators. +## Option 1: Cloud Browser Backend +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. -# Chat UI -Source: https://docs.browser-use.com/cloud/tutorials/chat-ui +### Setup +**1. Get your API key** - Clone and run in minutes. Next.js + Browser Use SDK v3. +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). -This tutorial walks through the [chat-ui-example](https://github.com/browser-use/chat-ui-example) — a Next.js app that lets users chat with a Browser Use agent in real time. We focus on the SDK integration, not the UI components. +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -The app has two pages: +**2. Configure Hermes** -1. **Home** — the user types a task, the app creates a session and sends the task. -2. **Session** — live browser preview, streaming messages, follow-ups, and recording download. +Run the setup wizard: -All SDK calls live in a single file: `src/lib/api.ts`. +```bash +hermes setup tools +``` -## Setup +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -```typescript api.ts -import { BrowserUse } from "browser-use-sdk/v3"; +Or configure manually — add your key to `~/.hermes/.env`: -// Server-only — no NEXT_PUBLIC_ prefix, never exposed to the browser -const apiKey = process.env.BROWSER_USE_API_KEY ?? ""; -export const client = new BrowserUse({ apiKey }); +```bash +BROWSER_USE_API_KEY=your_key_here ``` - The API key uses `BROWSER_USE_API_KEY` (no `NEXT_PUBLIC_` prefix) so it stays server-side. All SDK calls go through [server actions](https://nextjs.org/docs/app/guides/forms) — never call the SDK directly from client components. +And set the provider in `~/.hermes/config.yaml`: ---- +```yaml +browser: + cloud_provider: browser-use +``` -## 1. Create a session +**3. Use it** -```typescript actions.ts -"use server"; -import { client } from "./api"; +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: -export async function createSession() { - const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, - }); - return { id: session.id, liveUrl: session.liveUrl, status: session.status }; -} +``` +> Find the top trending repositories on GitHub today and summarize them ``` -- **`keepAlive: true`** keeps the session open after each task so the user can send follow-ups (default is `false`). -- **`enableRecording: true`** produces an MP4 video of the browser session. -- **`liveUrl`** is returned immediately — no waiting or extra call needed. +## Option 2: Browser Use CLI -The home page creates the session, navigates to the session page (passing `liveUrl` and the initial task via URL params), and the session page takes over from there: +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. -```typescript page.tsx -async function handleSend(message: string) { - const session = await createSession(); +### Setup - router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` - ); -} -``` +**1. Install the CLI** ---- +```bash +uv tool install browser-use +``` -## 2. Stream messages with `for await` +**2. Verify the installation** -Instead of polling `sessions.get()` and `sessions.messages()` separately, use `client.run()` — it streams messages and resolves when the task completes: +```bash +browser-use doctor +``` -```typescript session-context.tsx -const streamTask = useCallback(async (task: string) => { - const run = client.run(task, { sessionId }); +**3. Register the skill** - for await (const msg of run) { -setMessages((prev) => [...prev, msg]); - } +Register the Browser Use skill with the installed CLI: - // Iterator done — task reached terminal state - setSession(run.result); -}, [sessionId]); +```bash +browser-use skill install ``` -The `for await` loop yields each message as it arrives. When the loop ends, `run.result` contains the final session state (status, output, etc.). No separate status polling needed. +Or ask Hermes directly in chat to install it. + +**4. Authenticate for cloud browsers** -Wire it up in a `useEffect` to auto-run the initial task from URL params: +Authenticate with your API key: -```typescript session-context.tsx -useEffect(() => { - if (!initialTask) return; - sendMessage(initialTask); -}, []); +```bash +browser-use auth login ``` ---- +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -## 3. Follow-up tasks +**5. Use it** -Follow-ups call the same `streamTask` function — the stream already includes the user message, so no optimistic insert is needed: +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: -```typescript session-context.tsx -const sendMessage = useCallback(async (task: string) => { - await streamTask(task); -}, [streamTask]); +``` +> Use browser-use to open github.com/trending and summarize the top repos ``` -The SDK auto-sets `keepAlive: true` when targeting an existing session, so follow-up tasks work without extra config. - ---- +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). -## 4. Recording +## Agent Self-Registration -Fetch the MP4 URL after the session ends (recording was enabled in step 1): +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. -```typescript session-context.tsx -useEffect(() => { - if (!isTerminal) return; +Install the Browser Use CLI and skill: - client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); - }); -}, [isTerminal, sessionId]); +```bash +uv tool install browser-use +browser-use skill install ``` -`waitForRecording` polls for up to 15 seconds and returns presigned MP4 download URLs. Returns an empty array if the agent answered without opening a browser. +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** -## 5. Stop a task +For the cloud browser backend (Option 1): -```typescript actions.ts -export async function stopTask(id: string) { - await client.sessions.stop(id, { strategy: "task" }); -} +```bash +hermes config set BROWSER_USE_API_KEY ``` -Using `strategy: "task"` stops only the current task, keeping the session alive for follow-ups. - ---- +For CLI mode (Option 2), put the key in the agent's shell environment: -## 6. Session page - -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(); - - return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
- ); -} +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` ---- +### Claim the account (optional) -## Summary +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. -| Method | Purpose | -|--------|---------| -| `client.sessions.create()` | Create a session (returns `liveUrl` immediately) | -| `client.run()` | Send a task and stream messages with `for await` | -| `client.sessions.stop()` | Stop the current task | -| `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup -# Grow Therapy provider search -Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare +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. -This tutorial builds a provider search tool for [Grow Therapy](https://www.growtherapy.com) — a therapy marketplace that handles insurance credentialing for providers. We combine [structured output](https://docs.browser-use.com/cloud/agent/structured-output) with [deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script) to build a fast, repeatable search pipeline. +## REST flow -## What you'll build +### 1. Request a challenge -A script that: -1. Searches Grow Therapy's provider directory with filters (location, insurance, specialty) -2. Extracts therapist profiles with ratings and availability -3. Caches the search so you can sweep across geographies or specialties instantly +```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): -## Setup +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` -```python Python -import asyncio -import json -from pydantic import BaseModel -from browser_use_sdk.v3 import AsyncBrowserUse +Response: -client = AsyncBrowserUse() +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { z } from "zod"; -const client = new BrowserUse(); -``` +### 2. Solve the challenge -## 1. Define the output schema +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. -```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 - -class ProviderSearch(BaseModel): -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(), - })), - totalFound: z.number().nullable(), - location: z.string(), - specialty: z.string(), -}); +### 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"}' ``` -## 2. Create a workspace +Request body: -```python Python -workspace = await client.workspaces.create(name="grow-therapy-search") +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} ``` -```typescript TypeScript -const workspace = await client.workspaces.create({ name: "grow-therapy-search" }); + +Response: + +```json +{ + "api_key": "bu_..." +} ``` -## 3. Search for providers +Use the returned key for Browser Use Cloud API requests. -```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, -) +For example, create an API V4 run: -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() +```bash +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{"task":"Find the top Hacker News story"}' ``` -```typescript TypeScript -const 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.", - { workspaceId: workspace.id, schema: ProviderSearch }, -); -for (const p of result.output.providers) { - console.log(`${p.name} (${p.title})`); - console.log(` Specialties: ${p.specialties.join(", ")}`); - console.log(` Rating: ${p.rating}`); - console.log(` Next available: ${p.nextAvailable}`); -} -``` +See the [API V4 quick start](https://docs.browser-use.com/cloud/agent/quickstart). -## 4. Sweep across locations and specialties +## Claim the account -After the first run caches the search flow, sweep across different parameters at $0 LLM cost: +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: -```python Python -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") +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." ``` -```typescript TypeScript -const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; -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`); - } +Response: + +```json +{ + "claim_url": "https://..." } ``` ---- - -## Summary - -| Step | What happens | Cost | -|------|-------------|------| -| First search | Agent navigates Grow Therapy, caches the flow | ~$0.10 | -| 12 cached sweeps (4 cities x 3 specialties) | Script reruns with new params | **$0 LLM each** | -| Site layout change | [Auto-healing](https://docs.browser-use.com/cloud/agent/cache-script#auto-healing) regenerates the script | ~$0.10 | +The claim URL is valid for 1 hour. -Therapy platforms have dynamic UIs that can change frequently. [Auto-healing](https://docs.browser-use.com/cloud/agent/cache-script#auto-healing) ensures your cached scripts stay working without manual maintenance. +## CLI usage -## Next steps +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: -- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output) — Learn more about extracting typed data with Pydantic and Zod schemas. -- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) — Let a human review or interact with the browser mid-task, useful for auth flows or approving results before continuing. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script) — Deep dive into how caching and auto-healing work. +```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. # FAQ Source: https://docs.browser-use.com/cloud/faq @@ -2532,19 +1608,30 @@ Source: https://docs.browser-use.com/cloud/faq ## Which model should I use? -- **Claude Opus 4.6** (`claude-opus-4.6`) — most capable. Use for the hardest tasks that need maximum accuracy. -- **Claude Sonnet 4.6** (`claude-sonnet-4.6`, default) — best balance of capability and cost. Use for complex multi-step workflows. -- **GPT-5.4 mini** (`gpt-5.4-mini`) — fast and efficient. Good for simple, well-defined tasks. +- **Claude Opus 5** (`claude-opus-5`) — maximum intelligence for difficult, long-horizon work. +- **GPT-5.6** (`gpt-5.6`) — fast on complex tasks. +- **Gemini 3.5 Flash** (`gemini-3.5-flash`) — fast for simpler tasks. +- **MiniMax M3** (`minimax-m3`, default) — cheapest for simple and high-volume tasks. + +See [Models](https://docs.browser-use.com/cloud/agent/models) for the complete V4 picker and pricing. ## How do I get the live browser URL? -`live_url` is returned on session creation. Embed it in an iframe or open it in a browser. +The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -session = await client.sessions.create(task="Go to example.com") -print(session.live_url) +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +created = client.runs.create("Go to example.com") +client.runs.wait_for_completion(created.id) +events = client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +print(ready.data["live_view_url"]) ``` +Poll events until `browser.ready` appears if you need the URL while the run is still active. See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for a complete flow. + ## Getting blocked by a website Stealth and proxies are active by default. If you're still getting blocked: @@ -2558,25 +1645,21 @@ If it still doesn't work, contact support inside the [Cloud Dashboard](https://c The SDK auto-retries 429 responses with exponential backoff. If persistent, you may need more concurrent sessions — contact support. -## v2 vs v3 — which should I use? - -**v3 is the recommendation for everything.** It's a premium agent (not available in open source) that is significantly more capable than v2: +## V2 or V4 — which should I use? -- **Much better at complex tasks** and multi-step workflows -- **Much better at large data extraction** -- **File system** with persistent memory across tasks -- **Task scheduling** with 1,000+ integrations (Gmail, Slack, and more) +Use **V4** for difficult tasks where accuracy matters. It supports: -v2 is the closest to the open-source experience — pure browser automation, nothing else. If the open source already works great for your use case, v2 is the natural fit. For everything else, use v3. +- Run-focused API with a cheap status polling endpoint +- Conversation sessions with follow-ups +- Persistent workspaces and turn-scoped file attachments +- Incremental events for custom UIs and monitoring +- Per-run cost totals, cost caps, and optional judgement -```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse - -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse -``` +Use **V2** when tasks are simple and your priority is very low cost and +predictable speed. Its accuracy is substantially lower. +See Browser Use at #1 on the +[Odysseys benchmark](https://odysseysbench.com/leaderboard). # Agent (v2) Source: https://docs.browser-use.com/cloud/legacy/agent @@ -2587,7 +1670,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 | @@ -2621,15 +1703,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) ``` @@ -2662,8 +1744,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); @@ -2685,8 +1767,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 ``` @@ -2727,7 +1809,6 @@ console.log(run.result?.output); // final result after iteration | `op_vault_id` | `str` | 1Password vault ID for auto-fill credentials and 2FA. | | `metadata` | `dict[str, str]` | Custom metadata attached to the task. | - # Public share links (v2) Source: https://docs.browser-use.com/cloud/legacy/public-share @@ -2743,7 +1824,6 @@ const share = await client.sessions.createShare(session.id); console.log(share.shareUrl); ``` - # Skills Source: https://docs.browser-use.com/cloud/legacy/skills @@ -2767,8 +1847,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) ``` @@ -2789,8 +1869,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) ``` @@ -2831,7 +1911,6 @@ const result = await client.marketplace.execute(skillId, { parameters: { ... } } See [Pricing](https://browser-use.com/pricing) for skill costs. - # 1Password & 2FA Source: https://docs.browser-use.com/cloud/guides/1password @@ -2862,9 +1941,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) ``` @@ -2875,8 +1954,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); @@ -2886,17 +1965,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"], }, ); ``` @@ -2912,7 +1991,6 @@ When the agent encounters a login form: The agent never sees your actual credentials. The actual username, password, and 2FA codes are filled in programmatically — keeping your secrets hidden from the AI model. - # Secrets Source: https://docs.browser-use.com/cloud/guides/secrets @@ -2924,9 +2002,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 @@ -2936,8 +2014,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"], }, ); ``` @@ -2948,30 +2026,29 @@ 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-reference +Source: https://docs.browser-use.com/cloud/api-v4-overview ## Authentication @@ -2987,42 +2064,44 @@ Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/sett ## Base URL ``` -https://api.browser-use.com/api/v3 +https://api.browser-use.com/api/v4 ``` -## Quick example +## The core loop -```bash Create a session -curl -X POST https://api.browser-use.com/api/v3/sessions \ +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 Get session result (replace SESSION_ID) -curl https://api.browser-use.com/api/v3/sessions/SESSION_ID \ +```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" ``` -## Environment variable - -Set the key once so SDKs pick it up automatically: - -```bash -export 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 -Prefer the SDK? See the [Agent docs](https://docs.browser-use.com/cloud/agent/quickstart) — the SDK has all API endpoints available as methods, including `client.browsers.create()`. +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 Python -pip install browser-use-sdk -``` -```bash TypeScript -npm install browser-use-sdk +```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 quick start](https://docs.browser-use.com/cloud/agent/quickstart) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. # API key Source: https://docs.browser-use.com/cloud/api-v2-overview @@ -3046,66 +2125,3 @@ pip install browser-use-sdk ```bash TypeScript 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 8b1c30c10..3a2b3937e 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -1,15 +1,35 @@ -# Browser Use Cloud SDK +# Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task, get structured results back. SDKs for Python and TypeScript. Always use API v3 — v2 is legacy and uses different method names. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud has two products on the same managed browser +> infrastructure. **Agent** accepts a natural-language goal and completes the +> web task. **Browser** gives Playwright, Puppeteer, and other remote CDP +> clients direct control of a cloud browser. Both include stealth, residential +> proxies, profiles, and live observability. Auth uses +> `X-Browser-Use-API-Key` (keys start with `bu_`). - 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 -- 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. +- OpenAPI spec (v4): https://docs.browser-use.com/cloud/openapi/v4.json - 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. -**Always use v3.** v2 is legacy with different method names and should not be used for new projects. +**Choose API V4 for hard, high-accuracy tasks.** It is the recommended Agent API for new integrations and works especially well for long, complex workflows. + +**Choose API V2 for simple tasks when extremely low cost or predictable speed matters more than accuracy.** V2 accuracy is substantially lower than V4. + +Browser Use ranks #1 on the [Odysseys benchmark](https://odysseysbench.com/leaderboard). Use the benchmark when accuracy is the deciding factor. + +**Stopping standalone browsers:** Do not use `client.close()`, +`browser.close()`, or a dropped CDP connection as the API V4 stop operation. +Keep the browser session ID returned by `POST /api/v4/browsers`, then call +`PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and +refunds unused browser time. + +**Current TypeScript SDK typing:** Pass `model` explicitly (use `grok-4.5` for +the best price/accuracy balance). Whenever `browserSettings` is present, also +pass `proxyCountryCode`: use `"us"` to keep the default or `null` to disable +the managed proxy. New model strings can reach REST before the generated +TypeScript union; use `POST /api/v4/runs` directly if a listed model is rejected. Before writing code, check if `browser-use-sdk` is already installed. If so, upgrade to the latest version. If not, install it: - Python: `pip install --upgrade browser-use-sdk` @@ -22,43 +42,44 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [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. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a hosted agent or launch a cloud browser. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent -- [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. -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Run multiple tasks in the same browser session. -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Stream the agent's messages in real time to build custom UIs or monitor progress. -- [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. +- [Run a task](https://docs.browser-use.com/cloud/agent/quickstart): Give a high-accuracy browser agent a goal and get the result. +- [Models](https://docs.browser-use.com/cloud/agent/models): Choose a V4 model and understand its token pricing. +- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output): Ask for JSON and validate the V4 result in your application. +- [Sessions](https://docs.browser-use.com/cloud/agent/sessions): Continue one conversation across multiple V4 runs. +- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Persist files across V4 runs and conversations. +- [Scripts](https://docs.browser-use.com/cloud/agent/scripts): Save tested browser scripts in a workspace and reuse them on later runs. +- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the live browser, take over, then continue the same session. +- [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser -- [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. -- [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Residential proxies in 195+ countries. On by default. -- [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. +- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a cloud browser and connect to it from your code. +- [Stealth](https://docs.browser-use.com/cloud/browser/stealth): Best stealth on the planet. We fork Chromium to give agents access to all websites. +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Route API V4 agent runs through residential or custom proxies. +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview): Watch an API V4 run in real time or record its browser. +- [Playwright, Puppeteer, Selenium](https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium): Control a Browser Use cloud browser directly over 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. -- [2FA](https://docs.browser-use.com/cloud/guides/2fa): Best practices for handling two-factor authentication in automated browser sessions. +- [Profiles](https://docs.browser-use.com/cloud/guides/authentication): Reuse cookies and browser state in API V4 runs. +- [Sync local and cloud cookies](https://docs.browser-use.com/cloud/guides/profile-sync): Sync a local login, then use it in an API V4 run. +- [2FA](https://docs.browser-use.com/cloud/guides/2fa): Handle two-factor authentication in API V4 runs. ## 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. -- [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. -- [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. + +## Anthropic +- [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. ## 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. -- [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. +- [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. ## Legacy (v2) - [Agent (v2)](https://docs.browser-use.com/cloud/legacy/agent): V2 agent models and file handling. @@ -67,70 +88,8 @@ 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 v3 -- [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. +## 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 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/cloud/openapi/v4.json b/docs/cloud/openapi/v4.json index 0a15025f7..ec1f6424c 100644 --- a/docs/cloud/openapi/v4.json +++ b/docs/cloud/openapi/v4.json @@ -3105,9 +3105,12 @@ "enum": [ "glm-5.2", "grok-4.5", + "kimi-k3", "minimax-m3", "claude-opus-4.7", "claude-opus-4.8", + "claude-opus-5", + "claude-fable-5", "claude-sonnet-5", "gpt-5.5", "gpt-5.6", @@ -3751,7 +3754,7 @@ "anyOf": [ { "type": "string", - "maxLength": 255 + "maxLength": 100 }, { "type": "null" diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index b461a5ffc..8590383d9 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -1,10 +1,28 @@ --- title: Quick start -description: "State-of-the-art AI browser automation with stealth browsers, CAPTCHA solving, residential proxies, and managed infrastructure." +description: "Run a hosted agent or launch a cloud browser." icon: rocket --- -## 1. Install + + + Give an agent a task and get the result. + + + Launch a cloud browser and connect to it from your code. + + + +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: + +```bash +export BROWSER_USE_API_KEY=your_key +``` + +## Install the SDK + +Skip this step if you use curl. ```bash Python @@ -15,54 +33,88 @@ npm install browser-use-sdk ``` -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: +## Run a hosted agent -```bash -export BROWSER_USE_API_KEY=your_key + +```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; -## 2. Run your first task +const client = new BrowserUse(); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' +``` + + +## Control a browser + +Launch a browser, connect to its CDP URL, then stop it: ```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v3 import BrowserUse -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 = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -asyncio.run(main()) +# When finished: +client.browsers.stop(browser.id) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); + +// When finished: +await client.browsers.stop(browser.id); ``` - +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -Want a full working app? Check out the [Chat UI example](/cloud/tutorials/chat-ui). - -## Agent vs Browser - -| | **Agent** | **Browser** | -|---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | ✓ | ✓ | -| custom_proxy | ✓ | ✓ | -| profile_id | ✓ | ✓ | -| recording | ✓ | ✓ | -| workspace_id | ✓ | — | -| keep_alive | ✓ | — | -| screen size | — | ✓ | -| timeout | — | ✓ | +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) ---- +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` + + + + Closing Playwright, Puppeteer, or CDP does not stop the browser. Use + `client.browsers.stop(browser.id)` or call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. + -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). + + Give your coding agent the compact API V4 context. + diff --git a/docs/cloud/tutorials/grow-therapy-compare.mdx b/docs/cloud/tutorials/grow-therapy-compare.mdx index 2138e6f35..938637a74 100644 --- a/docs/cloud/tutorials/grow-therapy-compare.mdx +++ b/docs/cloud/tutorials/grow-therapy-compare.mdx @@ -4,7 +4,7 @@ description: "Search Grow Therapy for therapists by location, insurance, and spe icon: seedling --- -This tutorial builds a provider search tool for [Grow Therapy](https://www.growtherapy.com) — a therapy marketplace that handles insurance credentialing for providers. We combine [structured output](/cloud/agent/structured-output) with [deterministic rerun](/cloud/agent/cache-script) to build a fast, repeatable search pipeline. +This tutorial builds a provider search tool for [Grow Therapy](https://www.growtherapy.com) — a therapy marketplace that handles insurance credentialing for providers. We combine [structured output](/cloud/agent/structured-output) with [saved scripts](/cloud/agent/scripts) to build a fast, repeatable search pipeline. ## What you'll build @@ -163,14 +163,14 @@ for (const location of locations) { |------|-------------|------| | First search | Agent navigates Grow Therapy, caches the flow | ~$0.10 | | 12 cached sweeps (4 cities x 3 specialties) | Script reruns with new params | **$0 LLM each** | -| Site layout change | [Auto-healing](/cloud/agent/cache-script#auto-healing) regenerates the script | ~$0.10 | +| Site layout change | The [saved script](/cloud/agent/scripts) can be repaired and retested | ~$0.10 | -Therapy platforms have dynamic UIs that can change frequently. [Auto-healing](/cloud/agent/cache-script#auto-healing) ensures your cached scripts stay working without manual maintenance. +Therapy platforms have dynamic UIs that can change frequently. A later agent run can repair and retest the [saved script](/cloud/agent/scripts) when the site changes. ## Next steps - [Structured output](/cloud/agent/structured-output) — Learn more about extracting typed data with Pydantic and Zod schemas. - [Human in the loop](/cloud/agent/human-in-the-loop) — Let a human review or interact with the browser mid-task, useful for auth flows or approving results before continuing. -- [Deterministic rerun](/cloud/agent/cache-script) — Deep dive into how caching and auto-healing work. +- [Scripts](/cloud/agent/scripts) — Save, reuse, and repair browser workflows. diff --git a/docs/cloud/tutorials/integrations/claude-code.mdx b/docs/cloud/tutorials/integrations/claude-code.mdx index 50e025e56..ce057a7b5 100644 --- a/docs/cloud/tutorials/integrations/claude-code.mdx +++ b/docs/cloud/tutorials/integrations/claude-code.mdx @@ -68,26 +68,3 @@ browser-use auth status ### Claim the account (optional) If the human wants to see the account in the dashboard later, use the [claim endpoint](/cloud/agent-signup#claim-the-account). The returned claim URL is valid for 1 hour. - -## Pay with USDC via x402 - -Two ways to use x402 with Browser Use Cloud: - -- **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. - -Install the skill: - -```bash -npx skills add https://github.com/browser-use/browser-use --skill x402 -``` - -Then in Claude Code: - -``` -> /x402 -``` - -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. - -For the SDK API and protocol details, see the [x402 guide](/cloud/guides/x402). diff --git a/docs/docs.json b/docs/docs.json index 7b7fd70c8..fba7d1e15 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -75,21 +75,23 @@ { "group": "Agent", "icon": "robot", + "root": "cloud/agent/quickstart", "pages": [ "cloud/agent/quickstart", "cloud/agent/models", "cloud/agent/structured-output", - "cloud/agent/follow-up-tasks", - "cloud/agent/streaming", + "cloud/agent/sessions", "cloud/agent/workspaces", - "cloud/agent/cache-script", - "cloud/agent/human-in-the-loop" + "cloud/agent/scripts", + "cloud/agent/human-in-the-loop", + "cloud/agent/observability" ] }, { "group": "Browser", "icon": "globe", "pages": [ + "cloud/browser/quickstart", "cloud/browser/stealth", "cloud/browser/proxies", "cloud/browser/live-preview", @@ -122,20 +124,14 @@ ] }, "cloud/tutorials/integrations/openclaw", - "cloud/tutorials/integrations/hermes-agent", - "cloud/guides/mcp-server", - "cloud/guides/webhooks", - "cloud/guides/x402", - "cloud/tutorials/integrations/n8n" + "cloud/tutorials/integrations/hermes-agent" ] }, { "group": "Tutorials", "icon": "graduation-cap", "pages": [ - "cloud/tutorials/chat-ui", - "cloud/agent-signup", - "cloud/tutorials/grow-therapy-compare" + "cloud/agent-signup" ] }, "cloud/faq", @@ -184,6 +180,17 @@ "cloud/api-reference" ] }, + { + "group": "V3 guides and tutorials", + "pages": [ + "cloud/guides/mcp-server", + "cloud/guides/webhooks", + "cloud/guides/x402", + "cloud/tutorials/integrations/n8n", + "cloud/tutorials/chat-ui", + "cloud/tutorials/grow-therapy-compare" + ] + }, { "group": "Endpoints", "openapi": { @@ -470,7 +477,7 @@ }, { "source": "/cloud/tips/data/streaming", - "destination": "/cloud/agent/streaming" + "destination": "/cloud/agent/observability" }, { "source": "/cloud/tips/data/structured-output", @@ -982,7 +989,19 @@ }, { "source": "/tips/data/streaming", - "destination": "/cloud/agent/streaming" + "destination": "/cloud/agent/observability" + }, + { + "source": "/cloud/agent/follow-up-tasks", + "destination": "/cloud/agent/sessions" + }, + { + "source": "/cloud/agent/streaming", + "destination": "/cloud/agent/observability" + }, + { + "source": "/cloud/agent/cache-script", + "destination": "/cloud/agent/scripts" }, { "source": "/tips/integrations/playwright", @@ -1008,6 +1027,10 @@ "source": "/api-v3/*", "destination": "/cloud/api-v3" }, + { + "source": "/api-v4/*", + "destination": "/cloud/api-v4" + }, { "source": "/get-started/human-quickstart", "destination": "/cloud/quickstart" @@ -1105,4 +1128,4 @@ "destination": "/cloud/quickstart" } ] -} \ No newline at end of file +} diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index 6d2b70a3a..18e05de71 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -46,6 +46,10 @@ with open('$SCRIPT_DIR/docs.json') as f: BASE_URL = '$BASE_URL' SCRIPT_DIR = '$SCRIPT_DIR' +CLOUD_V3_ONLY = { + 'cloud/tutorials/chat-ui', + 'cloud/tutorials/grow-therapy-compare', +} def get_frontmatter(slug): import os @@ -68,6 +72,8 @@ def get_frontmatter(slug): return title, desc def format_entry(slug): + if '$product'.lower() == 'cloud' and slug in CLOUD_V3_ONLY: + return None title, desc = get_frontmatter(slug) if not title: return None @@ -117,6 +123,8 @@ for product_nav in d['navigation']['products']: for tab in product_nav['tabs']: if isinstance(tab, dict): tab_name = tab.get('tab', '') + if '$product'.lower() == 'cloud' and tab_name == 'API v3': + continue # Emit tab header for non-primary tabs to separate API sections if tab_name and tab_name != product_nav['tabs'][0].get('tab', ''): lines.append(f'') @@ -161,14 +169,22 @@ def extract_pages(obj): pages.extend(extract_pages(item)) return pages +CLOUD_V3_ONLY = { + 'cloud/tutorials/chat-ui', + 'cloud/tutorials/grow-therapy-compare', +} + for product_nav in d['navigation']['products']: if product_nav['product'].lower() == '$product'.lower(): if 'tabs' in product_nav: for tab in product_nav['tabs']: if isinstance(tab, dict): + if '$product'.lower() == 'cloud' and tab.get('tab') == 'API v3': + continue for g in tab.get('groups', []): for p in extract_pages(g): - print(p) + if '$product'.lower() != 'cloud' or p not in CLOUD_V3_ONLY: + print(p) if 'groups' in product_nav: for g in product_nav['groups']: for p in extract_pages(g): @@ -210,7 +226,6 @@ if block is not None: out.append(textwrap.dedent("\n".join(block))) sys.stdout.write("\n".join(out)) ' >> "$out" - echo "" >> "$out" done echo "Generated $out ($(wc -l < "$out") lines)" @@ -222,18 +237,38 @@ CLOUD_FULL="$SCRIPT_DIR/llms-full.txt" # Header cat > "$CLOUD_INDEX" << 'HEADER' -# Browser Use Cloud SDK +# Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task, get structured results back. SDKs for Python and TypeScript. Always use API v3 — v2 is legacy and uses different method names. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud has two products on the same managed browser +> infrastructure. **Agent** accepts a natural-language goal and completes the +> web task. **Browser** gives Playwright, Puppeteer, and other remote CDP +> clients direct control of a cloud browser. Both include stealth, residential +> proxies, profiles, and live observability. Auth uses +> `X-Browser-Use-API-Key` (keys start with `bu_`). - 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 -- 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. +- OpenAPI spec (v4): https://docs.browser-use.com/cloud/openapi/v4.json - 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. -**Always use v3.** v2 is legacy with different method names and should not be used for new projects. +**Choose API V4 for hard, high-accuracy tasks.** It is the recommended Agent API for new integrations and works especially well for long, complex workflows. + +**Choose API V2 for simple tasks when extremely low cost or predictable speed matters more than accuracy.** V2 accuracy is substantially lower than V4. + +Browser Use ranks #1 on the [Odysseys benchmark](https://odysseysbench.com/leaderboard). Use the benchmark when accuracy is the deciding factor. + +**Stopping standalone browsers:** Do not use `client.close()`, +`browser.close()`, or a dropped CDP connection as the API V4 stop operation. +Keep the browser session ID returned by `POST /api/v4/browsers`, then call +`PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and +refunds unused browser time. + +**Current TypeScript SDK typing:** Pass `model` explicitly (use `grok-4.5` for +the best price/accuracy balance). Whenever `browserSettings` is present, also +pass `proxyCountryCode`: use `"us"` to keep the default or `null` to disable +the managed proxy. New model strings can reach REST before the generated +TypeScript union; use `POST /api/v4/runs` directly if a listed model is rejected. Before writing code, check if `browser-use-sdk` is already installed. If so, upgrade to the latest version. If not, install it: - Python: `pip install --upgrade browser-use-sdk` @@ -249,7 +284,6 @@ HEADER # Append grouped nav entries generate_index "cloud" "Cloud" "/tmp/cloud_index_body.txt" cat /tmp/cloud_index_body.txt >> "$CLOUD_INDEX" -echo "" >> "$CLOUD_INDEX" echo "Generated $CLOUD_INDEX ($(wc -l < "$CLOUD_INDEX") lines)" # Full content @@ -273,7 +307,6 @@ HEADER generate_index "open-source" "Open Source" "/tmp/os_index_body.txt" cat /tmp/os_index_body.txt >> "$OS_INDEX" -echo "" >> "$OS_INDEX" echo "Generated $OS_INDEX ($(wc -l < "$OS_INDEX") lines)" generate_full "open-source" "Open Source" "$OS_FULL" diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 8da85e616..e8c4118ad 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -5,7 +5,19 @@ Source: https://docs.browser-use.com/cloud/quickstart -## 1. Install +Give an agent a task and get the result. +Launch a cloud browser and connect to it from your code. + +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: + +```bash +export BROWSER_USE_API_KEY=your_key +``` + +## Install the SDK + +Skip this step if you use curl. ```bash Python pip install browser-use-sdk @@ -14,1763 +26,1246 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: - -```bash -export BROWSER_USE_API_KEY=your_key -``` - -## 2. Run your first task +## Run a hosted agent ```python Python -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) +from browser_use_sdk.v4 import BrowserUse -asyncio.run(main()) +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); -``` - -Want a full working app? Check out the [Chat UI example](https://docs.browser-use.com/cloud/tutorials/chat-ui). - -## Agent vs Browser - -| | **Agent** | **Browser** | -|---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | ✓ | ✓ | -| custom_proxy | ✓ | ✓ | -| profile_id | ✓ | ✓ | -| recording | ✓ | ✓ | -| workspace_id | ✓ | — | -| keep_alive | ✓ | — | -| screen size | — | ✓ | -| timeout | — | ✓ | - ---- - -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 - - -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 +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` - - -# 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 \ +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{}' -``` - -Request body, optional (include a user email/name if available): - -```json -{ - "email": "user@example.com", - "name": "User Name" -} + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -Response: - -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` +## Control a browser -### 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 +Launch a browser, connect to its CDP URL, then stop it: -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` +```python Python +from browser_use_sdk.v3 import BrowserUse -Request body: +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} +# When finished: +client.browsers.stop(browser.id) ``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; -Response: +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); -```json -{ - "api_key": "bu_..." -} +// When finished: +await client.browsers.stop(browser.id); ``` +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -Use the returned key for Browser Use Cloud API requests. - -For example, create a browser session: +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{}' + -d '{"action":"stop"}' ``` -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + Closing Playwright, Puppeteer, or CDP does not stop the browser. Use + `client.browsers.stop(browser.id)` or call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. -## Claim the account + Give your coding agent the compact API V4 context. -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: +# Prompt for Vibecoders +Source: https://docs.browser-use.com/cloud/vibecoding -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` -Response: +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. -```json -{ - "claim_url": "https://..." -} +``` +https://docs.browser-use.com/cloud/llms.txt ``` -The claim URL is valid for 1 hour. - -# Introduction +# Run a task Source: https://docs.browser-use.com/cloud/agent/quickstart -The SDK is a thin wrapper around the [API v3 Reference](https://docs.browser-use.com/cloud/api-reference). Every endpoint in the API reference is available as an SDK method — `client.sessions`, `client.browsers`, `client.profiles`, `client.workspaces`, and `client.billing`. +Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: -`client.run()` creates a session, polls every 2 seconds until completion (up to 4 hours), and returns the result. It accepts all parameters from the [Create Session](https://docs.browser-use.com/cloud/api-v3/sessions/create-session) endpoint. The result is a [Session object](https://docs.browser-use.com/cloud/api-v3/sessions/get-session) — use `result.output` for the agent's response. +```bash +export BROWSER_USE_API_KEY=your_key +``` ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +client = BrowserUse() +run = client.runs.create( + "Find the top Hacker News story", + model="grok-4.5", +) +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run("List the top 20 posts on Hacker News today with their points"); -console.log(result.output); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -**What this agent can do:** -- **Data extraction** — scrape websites with thousands of listings -- **Form filling** — submit applications, fill out surveys, enter data -- **Multi-step workflows** — log in, navigate, click through flows, download files -- **Research** — search across multiple sites, compare results, summarize findings -- **Monitoring** — monitor a website and get notified if something changes -- **Testing** — test websites end-to-end with natural language instructions -- **Scheduling** — schedule tasks to run on a recurring basis -- **1,000+ integrations** — Gmail, Calendar, Notion, and more +Install the SDK with `pip install browser-use-sdk` or +`npm install browser-use-sdk`. Curl needs no installation. -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +Every new run implicitly creates a [session](https://docs.browser-use.com/cloud/agent/sessions) for its +conversation and live browser, plus a [workspace](https://docs.browser-use.com/cloud/agent/workspaces) for +persistent files. -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. +A task starts a run inside a session and the run reads and writes persistent workspace files +A task starts a run inside a session and the run reads and writes persistent workspace files +Give the compact context file to your coding agent. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` to select a model: +Pass one of these API strings as `model` when creating a run: + +| Model | API string | Input | Cache read | Output | BYOK | +| ----- | ---------- | ----: | ---------: | -----: | ---- | +| Claude Opus 5 | `claude-opus-5` | \$6.00 | \$0.60 | \$30.00 | Anthropic | +| Grok 4.5 | `grok-4.5` | \$2.40 | \$0.36 | \$7.20 | — | +| GPT-5.6 | `gpt-5.6` | \$6.00 | \$0.60 | \$36.00 | OpenAI | +| Gemini 3.5 Flash | `gemini-3.5-flash` | \$1.80 | \$0.18 | \$10.80 | Google | +| MiniMax M3 | `minimax-m3` | \$0.36 | \$0.072 | \$1.44 | — | -| Model | API String | Input (per 1M tokens) | Output (per 1M tokens) | -| ----- | ---------- | --------------------- | ---------------------- | -| Claude Sonnet 4.6 | `claude-sonnet-4.6` | \$3.60 | \$18.00 | -| Claude Opus 4.6 | `claude-opus-4.6` | \$6.00 | \$30.00 | -| GPT-5.4 mini | `gpt-5.4-mini` | \$0.90 | \$5.40 | +Token prices are USD per 1 million tokens. Browser sessions +(\$0.02/hour) and network traffic (\$5/GB managed proxy or \$0.20/GB +proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.com/pricing). - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **Grok 4.5** gives the best balance of price and accuracy. **MiniMax M3** is + the default and cheapest option; use **Claude Opus 5** when accuracy matters + most. + + New model strings can reach the API before the TypeScript SDK's generated + union. If TypeScript rejects a model listed above, call `POST /api/v4/runs` + directly for that model. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", +client = BrowserUse() +run = client.runs.create( + "Compare three project-management tools", + model="grok-4.5", ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const run = await client.runs.create({ + task: "Compare three project-management tools", + model: "grok-4.5", +}); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News", "model": "claude-sonnet-4.6"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` +## Bring your own key + +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring +Your Own Key**. V4 uses it automatically for matching models; no request flag +is needed. You pay the provider directly, plus a 0.2× Browser Use orchestration +fee. Grok and MiniMax currently use Browser Use-managed keys. # Structured output Source: https://docs.browser-use.com/cloud/agent/structured-output -Pass a Pydantic model (Python) or Zod schema (TypeScript) — `result.output` is automatically validated and converted to the typed object. - - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel -class Post(BaseModel): -name: str -points: int -comments: int +client = BrowserUse() -class HNPosts(BaseModel): -posts: list[Post] +class Story(BaseModel): + title: str + points: int -client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const Post = z.object({ - name: z.string(), +const client = new BrowserUse(); + +const Story = z.object({ + title: z.string(), points: z.number(), - comments: z.number(), }); -const HNPosts = z.object({ - posts: z.array(Post), +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); - -const client = new BrowserUse(); -const result = await client.run( - "List the top 20 posts on Hacker News today with their points", - { schema: HNPosts }, -); -for (const post of result.output.posts) { - console.log(`${post.name} (${post.points} pts, ${post.comments} comments)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` +V4 does not accept `output_schema` / `outputSchema`. Handle validation errors +and retry with a [session follow-up](https://docs.browser-use.com/cloud/agent/sessions) when needed. -# Follow-up tasks -Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks +# Sessions +Source: https://docs.browser-use.com/cloud/agent/sessions -When you pass a `session_id`, the session automatically stays alive between tasks. Each task runs a new agent that reuses the same browser — the agents don't share context, but the browser state (page, cookies, tabs) carries over. +A **session** holds the agent's conversation and can reuse its live browser. +One session ID can contain multiple runs. Every run creates a session implicitly +unless you pass an existing session ID. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser +One session ID containing three sequential runs, where each run continues the same conversation, workspace, and live browser -client = AsyncBrowserUse() +Pass `session_id` / `sessionId` to continue: -# Create a session, then run tasks inside it -session = await client.sessions.create() +```python Python +first = client.runs.create("Open Hacker News") +client.runs.wait_for_completion(first.id) -result1 = await client.run( -"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, +follow_up = client.runs.create( + "Now summarize the top story", + session_id=first.session_id, ) - -await client.sessions.stop(session.id) +result = client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); - -// Create a session, then run tasks inside it -const session = await client.sessions.create(); - -const result1 = await client.run("Go to amazon.com, search for laptops, and open the first result", { - sessionId: session.id, -}); -const result2 = await client.run("Extract the customer reviews", { - sessionId: session.id, +const first = await client.runs.create({ + task: "Open Hacker News", + model: "grok-4.5", }); +await client.runs.waitForCompletion(first.id); -await client.sessions.stop(session.id); +const followUp = await client.runs.create({ + task: "Now summarize the top story", + model: "grok-4.5", + sessionId: first.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); ``` -`sessions.create()` returns a `live_url` you can embed to watch each task execute — see [Live preview](https://docs.browser-use.com/cloud/browser/live-preview). To stream messages as each task runs, use `client.run()` with `for await` — see [Live messages](https://docs.browser-use.com/cloud/agent/streaming). - - Sessions time out after 15 minutes of inactivity by default. The maximum session duration is 4 hours. +Omit the session ID for a new conversation. Pass only a [workspace +ID](https://docs.browser-use.com/cloud/agent/workspaces) when you want a fresh conversation that keeps the +same files. +# Workspaces & files +Source: https://docs.browser-use.com/cloud/agent/workspaces -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace +Two independent sessions reading and writing people.csv, script.py, and output.json in one persistent workspace -Stream messages as the agent works — reasoning, tool calls, browser actions, and results. Each message has `role`, `type`, `summary`, `data`, and `screenshot_url`. See [List session messages](https://docs.browser-use.com/cloud/api-v3/sessions/list-session-messages) for all fields. +## Upload and attach a file ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() - -run = client.run("Find the top story on Hacker News") -async for msg in run: -print(f"[{msg.role}] {msg.summary}") +client = BrowserUse() +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -print(run.result.output) +run = client.runs.create( + "Find everyone in the CSV who works at Google", + workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], +) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); +const workspace = await client.workspaces.create({ + name: "research", +}); +const uploaded = await client.workspaces.upload( + workspace.id, + "people.csv", +); -const run = client.run("Find the top story on Hacker News"); -for await (const msg of run) { - console.log(`[${msg.role}] ${msg.summary}`); -} - -console.log(run.result.output); +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + model: "grok-4.5", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` -``` -[user] Find the top story on Hacker News -[assistant] Navigating to https://news.ycombinator.com/ -[tool] Browser Navigate: Navigated -[assistant] Analyzing browser state -[tool] Browser Analyze State: The top story is "Coding Agents Could Make Free Software Matter Again" -[tool] Done Autonomous: The top story on Hacker News is "Coding Agents Could Make Free Software Matter Again" -``` +Attachments are run-scoped. Reusing a workspace does not reattach every upload. -## Cancel a running task +## Retrieve created files -Use `stop(strategy="task")` to cancel the current task without destroying the session. The session goes back to `idle` and can accept a new task. +Ask the agent to save its output, then list the workspace: ```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 -# Session is now idle — send a different task or close it +files = client.workspaces.files( + workspace.id, + include_urls=True, +) +for file in files.files: + print(file.path, file.url) ``` ```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; - } +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, +); +for (const file of files.files) { + console.log(file.path, file.url); } -// Session is now idle — send a different task or close it ``` - `run.result` is only available **after** the iterator finishes (all messages consumed or task completes). If you break early from `async for` / `for await`, the task may still be running — call `stop(strategy="task")` to cancel it before sending a follow-up. - -## Manual polling - -If you need full control over the polling loop (e.g. custom interval, filtering): +Download URLs expire after 60 seconds. See the [workspace API +reference](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for pagination and +limits. -```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +# Scripts +Source: https://docs.browser-use.com/cloud/agent/scripts -client = AsyncBrowserUse() -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 +Scripts turn a successful browser run into a reusable +[workspace](https://docs.browser-use.com/cloud/agent/workspaces) asset. The agent writes and tests the +helper once. Later runs execute it first and repair it only when the site +changes. -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary +A first agent run saves and tests script.py and a README in a workspace; later runs reuse the script and repair it only when necessary -print(s.output) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +Create one workspace for the workflow, then use these prompts with the same +`workspace_id` / `workspaceId`. The [run code](https://docs.browser-use.com/cloud/agent/quickstart) does not +change. -const client = new BrowserUse(); -const session = await client.sessions.create({ - task: "Find the top story on Hacker News", -}); +## First run -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; - } +```text +Get the top five Hacker News stories as JSON. - const s = await client.sessions.get(session.id); - if (["idle", "stopped", "error", "timed_out"].includes(s.status)) { -console.log(s.output); -break; - } - await new Promise((r) => setTimeout(r, 2000)); -} +Then reproduce exactly what you did as helper functions or a script. Test it, +save it in this workspace, and add a README with instructions for using it again. ``` -## Related - -- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — embed the browser alongside your message stream -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — chain multiple tasks in one session while streaming each +## Later runs +```text +Use the existing workspace script to get the top ten Hacker News stories. +Follow its README. Only fix and retest the script if it no longer works. +``` -# Workspaces & files -Source: https://docs.browser-use.com/cloud/agent/workspaces - +This is faster and cheaper for repeated workflows, but it still starts an agent +and uses tokens. The script is reusable and self-healing—not zero-LLM execution. -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +# Human in the loop +Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it -## Upload a file +Use a human checkpoint for approvals, authentication, payments, or review. +After a run stops, get its `live_view_url` from the `browser.ready` event: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" +) +run = client.runs.wait_for_completion(run.id) -# Upload -await client.workspaces.upload(workspace.id, "people.csv") +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" +) +print(ready.data["live_view_url"]) -# Agent can now read it -result = await client.run( -"Read people.csv and tell me who works at Google", -workspace_id=workspace.id, +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, ) -print(result.output) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Upload -await client.workspaces.upload(workspace.id, "people.csv"); +const run = await client.runs.create({ + task: "Open the login page and stop for human review", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(run.id); -// Agent can now read it -const result = await client.run( - "Read people.csv and tell me who works at Google", - { workspaceId: workspace.id }, +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", ); -console.log(result.output); -``` - -You can upload multiple files at once: +console.log(ready?.data.live_view_url); -```python Python -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png") -``` -```typescript TypeScript -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png"); +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + model: "grok-4.5", + sessionId: run.sessionId, +}); ``` -## Download files +The same session preserves the conversation and workspace and reuses the live +browser while it is available. Treat live-view URLs as credentials. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +# Observability +Source: https://docs.browser-use.com/cloud/agent/observability -client = AsyncBrowserUse() -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, -) +Poll `runs.events()` with the previous cursor to receive only new events: + +```python Python +import time -# Download a single file -await client.workspaces.download(workspace.id, "posts.json", to="./posts.json") +after = None +while True: + page = client.runs.events(run.id, after=after) + for event in page.events: + print(event.type, event.data) + after = page.next_after if page.next_after is not None else after -# Or download everything -paths = await client.workspaces.download_all(workspace.id, to="./output") -for p in paths: -print(f"Downloaded: {p}") + status = client.runs.status(run.id).status.value + if status in {"completed", "failed", "cancelled"}: + break + time.sleep(1) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-workspace" }); - -// Agent creates a file -const result = await client.run( - "Go to Hacker News and save the top 3 posts as posts.json", - { workspaceId: workspace.id }, -); - -// Download a single file -await client.workspaces.download(workspace.id, "posts.json", { to: "./posts.json" }); +let after: number | undefined; +while (true) { + const page = await client.runs.events(run.id, { after }); + for (const event of page.events) { + console.log(event.type, event.data); + } + after = page.nextAfter ?? after; -// Or download everything -const paths = await client.workspaces.downloadAll(workspace.id, { to: "./output" }); -for (const p of paths) { - console.log(`Downloaded: ${p}`); + const { status } = await client.runs.status(run.id); + if (["completed", "failed", "cancelled"].includes(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); } ``` -## Manage workspaces +Events cover run lifecycle, model calls, browser readiness, tool activity, +artifacts, and completion. See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) +for the complete response shape. -```python Python -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) -await client.workspaces.delete(workspace_id) -``` -```typescript TypeScript -const workspace = await client.workspaces.get(workspaceId); -const updated = await client.workspaces.update(workspaceId, { name: "renamed" }); -const response = await client.workspaces.list(); -for (const w of response.items) { - console.log(w.id, w.name); -} -await client.workspaces.delete(workspaceId); -``` +# Browser quickstart +Source: https://docs.browser-use.com/cloud/browser/quickstart -## Organize with prefixes -Use `prefix` to organize files into directories within a workspace: +Every browser includes stealth, proxies, live preview, and recording. Its +**CDP URL** is a WebSocket endpoint for remotely controlling Chrome. -```python Python -# Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") +Your code using a CDP URL to connect to and control a cloud browser that accesses the web +Your code using a CDP URL to connect to and control a cloud browser that accesses the web -# List files in a subdirectory -files = await client.workspaces.files(workspace.id, prefix="reports/") -for f in files.files: -print(f.path, f.size) +Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and +export it: -# Download only files from a subdirectory -await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") +```bash +export BROWSER_USE_API_KEY=your_key ``` -```typescript TypeScript -// Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", { prefix: "reports/" }); -// List files in a subdirectory -const files = await client.workspaces.files(workspace.id, { prefix: "reports/" }); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Download only files from a subdirectory -await client.workspaces.downloadAll(workspace.id, { to: "./output", prefix: "reports/" }); -``` +## Install the SDK -## List and delete files +Skip this step if you use curl. -```python Python -# List all files -files = await client.workspaces.files(workspace.id) -for f in files.files: -print(f.path, f.size) - -# Delete a single file -await client.workspaces.delete_file(workspace.id, path="old-report.pdf") - -# Check workspace storage usage -size = await client.workspaces.size(workspace.id) -print(f"Used: {size.used_bytes} bytes") +```bash Python +pip install browser-use-sdk ``` -```typescript TypeScript -// List all files -const files = await client.workspaces.files(workspace.id); -for (const f of files.files) { - console.log(f.path, f.size); -} - -// Delete a single file -await client.workspaces.deleteFile(workspace.id, "old-report.pdf"); - -// Check workspace storage usage -const size = await client.workspaces.size(workspace.id); -console.log(`Used: ${size.usedBytes} bytes`); +```bash TypeScript +npm install browser-use-sdk ``` -## Cloud dashboard - -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). - - Deleting a workspace permanently removes all its files. This cannot be undone. - - -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script - - -Deterministic rerun lets you run a browser task once with a full agent, then **re-execute the same task instantly** using a cached script — no LLM, up to 99% cheaper. - -## Quick start - -Use `@{{double brackets}}` around values that can change between runs. The first call runs the full agent. Every subsequent call with the same template uses the cached script. +## Launch a browser ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-scraper") +from browser_use_sdk.v3 import BrowserUse -# 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), -) +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -# 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), -) +# When finished: +client.browsers.stop(browser.id) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "my-scraper" }); - -// First call — agent explores, creates script (~$0.10, ~60s) -const result = await client.run( - "Get the top @{{5}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); +console.log(browser.cdpUrl); -// Second call — cached script, different param ($0 LLM, ~5s) -const result2 = await client.run( - "Get the top @{{10}} stories from https://news.ycombinator.com as JSON", - { workspaceId: workspace.id }, -); +// When finished: +await client.browsers.stop(browser.id); ``` +```bash curl +browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -## How it works - -The brackets mark which parts are parameters: +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) +echo "$BROWSER_USE_CDP_URL" +# When finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' ``` -"Get prices from @{{example.com}} for @{{electronics}}" -``` - -- `@{{example.com}}` → parameter 1 -- `@{{electronics}}` → parameter 2 -The system strips the values to create a **template**: `"Get prices from @{{}} for @{{}}"`. -Template `"Get prices from @{{}} for @{{}}"` is hashed to a unique ID like `a7f3b2c1`. -The system checks the workspace for `scripts/a7f3b2c1.py`. -If no script exists, the full agent runs your task. After completing it, the agent saves a standalone Python script that reproduces the result deterministically — no AI needed. -If the script exists, it runs directly with the new parameter values. No agent, no LLM. Just the script in a sandbox with browser and proxy. + `browser.close()`, disconnecting CDP, or `client.close()` does not stop the + managed browser. Use `client.browsers.stop(browser.id)` or call + `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. -## Auto-detection + +Use the CDP URL with Playwright or Puppeteer. +Configure proxies, screen size, recording, and timeout. -Caching activates **automatically** when both conditions are met: -- The task contains `@{{` and `}}` -- A `workspace_id` is provided - -No extra flags needed. You can override with `cache_script`: +# Stealth +Source: https://docs.browser-use.com/cloud/browser/stealth -| Value | Behavior | -|-------|----------| -| `None` (default) | Auto-detect from `@{{brackets}}` + workspace | -| `True` | Force-enable, even without brackets | -| `False` | Force-disable, even if brackets are present | -## Examples +See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). -### Parameterized scraping +## What's included -Run once, then loop over different keywords at $0 LLM each: +Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. -```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), -) +- **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. -# 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}") -``` -```typescript TypeScript -// Agent figures out how to scrape intro.co on first call -let result = await client.run( - "Go to @{{https://intro.co/marketplace}} and get all @{{logistics}} experts as JSON", - { workspaceId: workspace.id }, -); +## Residential proxies -// 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 }, - ); - console.log(`${keyword}: ${result.output}`); -} -``` +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. -### No parameters — cache the exact task +# Proxies +Source: https://docs.browser-use.com/cloud/browser/proxies -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), -) +A US residential proxy is enabled by default. The browser's traffic passes +through that residential IP before reaching the website, so the website sees +the proxy's location—not your server's. -# Same task again — cached -result2 = await client.run( -"Get the current Bitcoin price from coinmarketcap.com @{{}}", -workspace_id=str(workspace.id), -) -``` -```typescript TypeScript -let result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); +A cloud browser routing its traffic through a residential proxy before reaching a website +A cloud browser routing its traffic through a residential proxy before reaching a website -// Same task again — cached -result = await client.run( - "Get the current Bitcoin price from coinmarketcap.com @{{}}", - { workspaceId: workspace.id }, -); -``` +Set `browser_settings` / `browserSettings` when you create a V4 run to choose +another country: -### Multiple parameters + The current TypeScript SDK type requires `proxyCountryCode` whenever + `browserSettings` is present. Use `"us"` to keep the default, or `null` to + disable the managed proxy. ```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), -) +from browser_use_sdk.v4 import BrowserUse -# 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), +client = BrowserUse() +run = client.runs.create( + "Get the iPhone 16 price on amazon.de", + browser_settings={"proxyCountryCode": "de"}, ) ``` ```typescript TypeScript -let result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{Germany,France,Japan}}", - { workspaceId: workspace.id }, -); +import { BrowserUse } from "browser-use-sdk/v4"; -// Different countries — cached -result = await client.run( - "Go to https://help.netflix.com/en/node/24926/ax and get subscription prices for @{{US,UK,Brazil}}", - { workspaceId: workspace.id }, -); +const client = new BrowserUse(); +const run = await client.runs.create({ + task: "Get the iPhone 16 price on amazon.de", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "de" }, +}); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Get the iPhone 16 price on amazon.de", + "browserSettings":{"proxyCountryCode":"de"}}' ``` -### Force enable / disable +## Disable proxies -```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, -) +Pass `null` for QA or internal sites that do not need a residential proxy: -# Force-disable even with brackets -result = await client.run( -"Explain what @{{templates}} means in Jinja", -workspace_id=str(workspace.id), -cache_script=False, +```python Python +run = client.runs.create( + "Test my staging site", + browser_settings={"proxyCountryCode": None}, ) ``` ```typescript TypeScript -// Force-enable without brackets -let result = await client.run( - "Get the top stories from Hacker News", - { workspaceId: workspace.id, cacheScript: true }, -); - -// Force-disable even with brackets -result = await client.run( - "Explain what @{{templates}} means in Jinja", - { workspaceId: workspace.id, cacheScript: false }, -); +const run = await client.runs.create({ + task: "Test my staging site", + model: "grok-4.5", + browserSettings: { proxyCountryCode: null }, +}); ``` -## Inspecting cached scripts +## Custom proxy -You can download and inspect the scripts the agent created: +Custom HTTP and SOCKS5 proxies are available on paid plans: ```python Python -files = await client.workspaces.files(workspace.id, prefix="scripts/") -for f in files.files: -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") +run = client.runs.create( + "Check the account dashboard", + browser_settings={ + "customProxy": { + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + "ignoreCertErrors": False, + } + }, +) ``` ```typescript TypeScript -const files = await client.workspaces.files(workspace.id, { prefix: "scripts/" }); -for (const f of files.files) { - console.log(`${f.path} (${f.size} bytes)`); -} +const run = await client.runs.create({ + task: "Check the account dashboard", + model: "grok-4.5", + browserSettings: { + proxyCountryCode: "us", + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + ignoreCertErrors: false, + }, + }, +}); ``` -## Auto-healing - -Cached scripts can break when a website changes its layout, adds new elements, or alters its structure. Auto-healing detects these failures and automatically regenerates the script. +A custom proxy overrides `proxyCountryCode` and must be passed again when a +follow-up provisions a new browser. See the [Create run +reference](https://docs.browser-use.com/cloud/api-v4/runs/create-run) for the complete settings object. -### How it works - -When a cached script runs, the system validates its output: +# Live preview & recording +Source: https://docs.browser-use.com/cloud/browser/live-preview -1. **Fast checks** (no LLM) — detects empty results, error fields in JSON, or exception keywords in output. -2. **LLM judge** — if fast checks pass, a lightweight model validates whether the output looks correct for the original task. -3. **Heal** — if validation fails, the full agent re-runs the task and saves an updated script. -Auto-healing is **limited to 1 attempt per run** to prevent runaway costs. If the healed script also fails, the output is returned as-is. +The `browser.ready` event contains the live browser URL: -### Cost impact +```python Python +from browser_use_sdk.v4 import BrowserUse -| Scenario | LLM cost | -|----------|----------| -| Cached script succeeds | **$0** | -| Cached script fails, auto-heals | ~$0.05–1.00 (one full agent run) | -| Healed script also fails | Same as above (returns best-effort output) | +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) -Auto-healing is enabled by default for all cached scripts. No configuration needed. +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" +) +print(ready.data["live_view_url"]) +``` +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; -## Cost comparison +const client = new BrowserUse(); +const run = await client.runs.create({ + task: "Find the top Hacker News story", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(run.id); -| | LLM cost | Browser + proxy | Time | -|---|---|---|---| -| First call (agent) | ~$0.05–1.00 | Yes | ~30–120s | -| Cached calls | **$0** | Yes | ~3–10s | +const events = await client.runs.events(run.id, { + limit: 100, +}); +const ready = events.events.find( + (event) => event.type === "browser.ready", +); +console.log(ready?.data.live_view_url); +``` -The browser and proxy still run for cached calls (the script may need them), so there is a small infrastructure cost per execution. LLM cost drops to zero. +Poll [run events](https://docs.browser-use.com/cloud/agent/observability) if you need the URL as soon as +the browser starts. +## Embed the live browser -# Human in the loop -Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop +```html + +``` +The URL is hosted on `live.browser-use.com`. Add that origin to your +Content Security Policy's `frame-src` directive when needed. Treat the URL as +a credential: anyone with it can interact with the active browser. -## Use cases -- Human enters payment info or approves a transaction, agent handles the rest -- Human navigates a complex auth flow, then hands back to agent -- Human reviews what the agent did before the agent continues +## Recording - Sessions time out after 15 minutes of inactivity. The maximum session duration is 4 hours. If the human needs more time, send a lightweight follow-up task (e.g. "wait") to reset the inactivity timer. +Enable recording when the run creates its browser: -## Flow +```python Python +run = client.runs.create( + "Test the checkout flow", + browser_settings={"record": True}, +) +``` +```typescript TypeScript +const run = await client.runs.create({ + task: "Test the checkout flow", + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, +}); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Test checkout","browserSettings":{"record":true}}' +``` -1. Create a session — it stays alive automatically when you pass `session_id` to `run()` -2. Run an agent task -3. Human interacts with the live browser -4. Send a new follow-up task +The MP4 becomes available in the Dashboard after the browser stops. API runs +default to recording off, and Zero Data Retention projects never record. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +# Playwright, Puppeteer, Selenium +Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium -client = AsyncBrowserUse() -# 1. Create a session -session = await client.sessions.create() -print(f"Live view: {session.live_url}") +Every browser 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. -# 2. Agent does the first part -result = await client.run( -"Go to amazon.com and search for noise cancelling headphones", -session_id=session.id, -) -print(result.output) + This page is for direct browser control. To give an AI agent a goal instead, + [create an API V4 run](https://docs.browser-use.com/cloud/agent/quickstart). -# 3. Human opens live_url and picks a product -input("Press Enter after you've selected a product in the live view...") +## 1. Create a browser -# 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, -) -print(result.output) +```bash +session=$(curl -sS https://api.browser-use.com/api/v4/browsers \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"proxyCountryCode":"us"}') -# Clean up -await client.sessions.stop(session.id) +export BROWSER_SESSION_ID=$(echo "$session" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$session" | jq -r .cdpUrl) ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; -const client = new BrowserUse(); +## 2. Connect over CDP -// 1. Create a session -const session = await client.sessions.create(); -console.log(`Live view: ${session.liveUrl}`); +### Playwright -// 2. Agent does the first part -const searchResult = await client.run( - "Go to amazon.com and search for noise cancelling headphones", - { sessionId: session.id }, -); -console.log(searchResult.output); +```python Python +import os +from playwright.sync_api import sync_playwright -// 3. Human opens liveUrl and picks a product -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => - rl.question("Press Enter after you've selected a product in the live view...", resolve), -); -rl.close(); +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(os.environ["BROWSER_USE_CDP_URL"]) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) +``` +```typescript TypeScript +import { chromium } from "playwright"; -// 4. Agent continues where the human left off -const result = await client.run( - "Get the details of the selected product — name, price, and rating", - { sessionId: session.id }, +const browser = await chromium.connectOverCDP( + process.env.BROWSER_USE_CDP_URL!, ); -console.log(result.output); - -// Clean up -await client.sessions.stop(session.id); +const page = browser.contexts()[0].pages()[0]; +await page.goto("https://example.com"); +console.log(await page.title()); ``` +### Puppeteer +```typescript +import puppeteer from "puppeteer-core"; -# Introduction Stealth -Source: https://docs.browser-use.com/cloud/browser/stealth +const browser = await puppeteer.connect({ + browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!, +}); +const [page] = await browser.pages(); +await page.goto("https://example.com"); +console.log(await page.title()); +``` +### Selenium -See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark). +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. -## What's included +## 3. Stop the browser -Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. +```bash +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$BROWSER_SESSION_ID" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` -- **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. + `browser.close()` and disconnecting CDP do not stop the managed browser. + Call `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. -## Residential proxies +See [Create browser session](https://docs.browser-use.com/cloud/api-v4/browsers/create-browser-session) and +[Update browser session](https://docs.browser-use.com/cloud/api-v4/browsers/update-browser-session) for +every browser setting and response field. -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. +# Profiles +Source: https://docs.browser-use.com/cloud/guides/authentication -# Proxies -Source: https://docs.browser-use.com/cloud/browser/proxies +A profile persists cookies, local storage, and login state across browsers. +Log in once, save the profile, then reuse it to start future browsers already +logged in. +One login saved as a profile and reused by multiple future browsers +One login saved as a profile and reused by multiple future browsers -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. +Create or select one under [Dashboard → Profiles](https://cloud.browser-use.com/settings?tab=profiles), +then pass its ID in V4 browser settings: ```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 +from browser_use_sdk.v4 import BrowserUse -# With an agent: -# result = await client.run("Get the price of iPhone 16 on amazon.de", proxy_country_code="de") +client = BrowserUse() +run = client.runs.create( + "Open my account dashboard and summarize it", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) +result = client.runs.wait_for_completion(run.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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" }); +const run = await client.runs.create({ + task: "Open my account dashboard and summarize it", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, +}); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); +``` +```bash curl +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"task":"Summarize my account dashboard", + "browserSettings":{"profileId":"YOUR_PROFILE_ID"}}' ``` -## Disable proxies +Use one profile per end user. Follow-ups in the same [session](https://docs.browser-use.com/cloud/agent/sessions) +reuse the live browser; later sessions can load the same profile again. -If your use case does not need proxies, for example QA testing. +For the fastest setup, [sync an existing local login](https://docs.browser-use.com/cloud/guides/profile-sync). -```python Python -browser = await client.browsers.create(proxy_country_code=None) +# Sync local and cloud cookies +Source: https://docs.browser-use.com/cloud/guides/profile-sync -# 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 }); -``` +Run the profile sync helper: -## Custom proxy +```bash +export BROWSER_USE_API_KEY=your_key +curl -fsSL https://browser-use.com/profile.sh | sh +``` -Bring your own proxy server (HTTP or SOCKS5). +Choose the accounts to sync, then use the returned profile ID: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -browser = await client.browsers.create( -custom_proxy={ - "host": "proxy.example.com", - "port": 8080, - "username": "user", - "password": "pass", -}, +client = BrowserUse() +run = client.runs.create( + "Check my LinkedIn messages", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, ) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const browser = await client.browsers.create({ - customProxy: { -host: "proxy.example.com", -port: 8080, -username: "user", -password: "pass", +const run = await client.runs.create({ + task: "Check my LinkedIn messages", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", }, }); ``` +The profile supplies cookies and local storage without putting credentials in +the prompt. Re-sync when the site's login expires. -# Live preview & recording -Source: https://docs.browser-use.com/cloud/browser/live-preview +# 2FA +Source: https://docs.browser-use.com/cloud/guides/2fa - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). +The most reliable options are a saved profile or a human checkpoint. -`liveUrl` is returned on session creation. +## Reuse a logged-in profile -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +[Sync your local login](https://docs.browser-use.com/cloud/guides/profile-sync), then load that profile in +the run: -client = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +```python Python +run = client.runs.create( + "Download my latest invoice", + browser_settings={"profileId": "YOUR_PROFILE_ID"}, +) ``` ```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", +const run = await client.runs.create({ + task: "Download my latest invoice", + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); -console.log(session.liveUrl); ``` -`liveUrl` is also returned when creating a standalone browser session: +This avoids another 2FA challenge while the site's cookies remain valid. + +## Let a human take over + +Ask the first run to stop at the 2FA screen, get its `live_view_url` from the +[`browser.ready` event](https://docs.browser-use.com/cloud/agent/human-in-the-loop), and have the user enter +the code. Then continue with the same session: ```python Python -browser = await client.browsers.create() -print(browser.live_url) +first = client.runs.create( + "Open the login page and stop at the 2FA prompt", +) +client.runs.wait_for_completion(first.id) + +next_run = client.runs.create( + "Continue after login and download the invoice", + session_id=first.session_id, +) ``` ```typescript TypeScript -const browser = await client.browsers.create(); -console.log(browser.liveUrl); +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", + model: "grok-4.5", +}); +await client.runs.waitForCompletion(first.id); + +const nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + model: "grok-4.5", + sessionId: first.sessionId, +}); ``` -## Embed live browser into your app +See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for retrieving and +embedding the live browser URL. Never put passwords or TOTP secrets directly +in a prompt. -Useful for human interaction or to see live what's happening. +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code -```html - + +[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. + +## Setup + +**1. Install the CLI** + +```bash +uv tool install browser-use ``` -The live URL is hosted on `live.browser-use.com`. If your app sets a Content Security Policy, add it to your `frame-src` directive: +**2. Verify the installation** +```bash +browser-use doctor ``` -Content-Security-Policy: frame-src 'self' https://live.browser-use.com; + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install ``` -For responsive sizing, use CSS instead of fixed dimensions: +**4. Authenticate for cloud browsers** -```html - +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login ``` -## Customize +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. -Append query parameters to the `liveUrl`: +**5. Use it** -| Parameter | Values | Description | -|-----------|--------|-------------| -| `theme` | `light`, `dark` (default) | Light or dark mode | -| `ui` | `false` | Hide the browser chrome (URL bar, tabs) | +Claude Code uses its bash tool to run CLI commands directly: ``` -https://live.browser-use.com?wss=...&theme=light -https://live.browser-use.com?wss=...&ui=false +> Use browser-use to open github.com/trending and summarize the top repos ``` -## Recording +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). - `waitForRecording` / `wait_for_recording` requires the **v3 SDK** (`from browser_use_sdk.v3 import AsyncBrowserUse` / `import { BrowserUse } from "browser-use-sdk/v3"`). +## Agent Self-Registration -Enable recording to get an MP4 video of the browser session. Only available when the agent actually opens a browser — tasks answered without browsing produce no recording. If you run multiple tasks in the same session (with `keep_alive`), you may get multiple recordings. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -result = await client.run( -"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 -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const result = await client.run("Check how many GitHub stars browser-use has", { - enableRecording: true, -}); - -// Waits up to 15s for recording to be ready. Returns [] if no browser was opened. -const urls = await client.sessions.waitForRecording(result.id); -for (const url of urls) { - console.log(url); // presigned MP4 download URL -} -``` - -For standalone browser sessions, pass `enable_recording` when creating the browser and retrieve the URL after stopping it: - -```python Python -browser = await client.browsers.create(enable_recording=True) -# ... use the browser via CDP ... -stopped = await client.browsers.stop(browser.id) -print(stopped.recording_url) # presigned MP4 download URL -``` -```typescript TypeScript -const browser = await client.browsers.create({ enableRecording: true }); -// ... use the browser via CDP ... -const stopped = await client.browsers.stop(browser.id); -console.log(stopped.recordingUrl); // presigned MP4 download URL -``` - - Recording URLs are presigned and **expire after 1 hour**. Download or serve the recording promptly. If you need to access it later, save the MP4 to your own storage. - -## Related - -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming) — stream the agent's messages alongside the live browser view -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — chain tasks in one session while watching live - - - -# Playwright, Puppeteer, Selenium -Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium - - -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. - -## 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 is automatically stopped when the WebSocket disconnects -``` -```typescript TypeScript -import { chromium } from "playwright"; - -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; - -const browser = await chromium.connectOverCDP(WSS_URL); -const page = browser.contexts()[0].pages()[0]; -await page.goto("https://example.com"); -console.log(await page.title()); -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 - -| 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, get a `cdp_url`, and connect with Playwright or Puppeteer. - -### Playwright - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse -from playwright.async_api import async_playwright - -client = AsyncBrowserUse() -browser = await client.browsers.create() -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() - -await client.browsers.stop(browser.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { chromium } from "playwright"; - -const client = new BrowserUse(); -const browser = await client.browsers.create(); -console.log(browser.cdpUrl); // https://uuid.cdpN.browser-use.com -console.log(browser.liveUrl); // https://live.browser-use.com?wss=... - -const pwBrowser = await chromium.connectOverCDP(browser.cdpUrl); -const page = pwBrowser.contexts()[0].pages()[0]; -await page.goto("https://example.com"); -console.log(await page.title()); -await pwBrowser.close(); - -await client.browsers.stop(browser.id); -``` - -### Puppeteer - -```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 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); -``` - - Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires. - - -# Profiles -Source: https://docs.browser-use.com/cloud/guides/authentication - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -profile = await client.profiles.create(name="user-id-1") -# or search existing -# profile = (await client.profiles.list(query="user-id-1")).items[0] -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Check browser-use github stars", session_id=session.id) -print(result.output) - -# Always stop the session to persist profile state -await client.sessions.stop(session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const profile = await client.profiles.create({ name: "user-id-1" }); -// or search existing -// const profile = (await client.profiles.list({ query: "user-id-1" })).items[0]; -const session = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Check browser-use github stars", { - sessionId: session.id, -}); -console.log(result.output); - -// Always stop the session to persist profile state -await client.sessions.stop(session.id); -``` - -View your profile IDs at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=profiles). - -## Manage profiles - -```python Python -# Create -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) - -# Search by name -response = await client.profiles.list(query="user-id-1") -profile = response.items[0] # first match - -# Get one by ID -profile = await client.profiles.get(profile_id) - -# Update -await client.profiles.update(profile_id, name="renamed") - -# Delete -await client.profiles.delete(profile_id) -``` -```typescript TypeScript -// Create -const profile = await client.profiles.create({ name: "work-account" }); - -// List all -const response = await client.profiles.list(); -for (const p of response.items) { - console.log(p.id, p.name); -} - -// Search by name -const results = await client.profiles.list({ query: "user-id-1" }); -const found = results.items[0]; // first match - -// Get one by ID -const fetched = await client.profiles.get(profileId); - -// Update -await client.profiles.update(profileId, { name: "renamed" }); - -// Delete -await client.profiles.delete(profileId); -``` - -## Usage patterns - -- **Per-user profiles:** Create one profile per end-user. Query by name to get the profile ID, or store a mapping between your users and their profile IDs in your database. - - 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. - - -# Sync local and cloud cookies -Source: https://docs.browser-use.com/cloud/guides/profile-sync +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. +Install the Browser Use CLI and skill: ```bash -export BROWSER_USE_API_KEY=your_key && curl -fsSL https://browser-use.com/profile.sh | sh -``` - -This opens a browser where you select which accounts to sync. After syncing, you receive a `profile_id` to use in your tasks. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -session = await client.sessions.create(profile_id="your_synced_profile_id") -result = await client.run("Check my LinkedIn messages", session_id=session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse(); -const session = await client.sessions.create({ profileId: "your_synced_profile_id" }); -const result = await client.run("Check my LinkedIn messages", { - sessionId: session.id, -}); -``` - - -# 2FA -Source: https://docs.browser-use.com/cloud/guides/2fa - - -Sites with 2FA block automated logins. Here are four approaches — pick the one that fits your setup. - -| Approach | Best for | Complexity | -|---|---|---| -| [Profiles (login once)](#1-profiles--login-once-reuse-cookies) | Sites with long-lived cookies | Lowest | -| [Human in the loop](#2-human-in-the-loop) | One-off tasks, complex auth flows | Low | -| [Agent Mail](#3-agent-mail) | Email-based 2FA, end-client automation | Medium | -| [TOTP secret in prompt](#4-totp-secret-in-prompt) | Authenticator app 2FA (Google Authenticator, Authy) | Medium | - ---- - -## 1. Profiles — login once, reuse cookies - -Login manually once (or let the agent do it), then save the browser state as a profile. Future sessions reuse the cookies — no 2FA prompt as long as the cookies are valid. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# Create a profile and a session -profile = await client.profiles.create(name="my-account") -session = await client.sessions.create(profile_id=profile.id) -print(f"Live view: {session.live_url}") - -# Option A: human logs in via live view -input("Log in and complete 2FA in the live view, then press Enter...") - -# Option B: let the agent log in -# await client.run("Log in to example.com with user@example.com / password123", session_id=session.id) - -# Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id) - -# Next time: reuse the profile, no 2FA needed -session = await client.sessions.create(profile_id=profile.id) -result = await client.run("Go to example.com/dashboard and get my balance", 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(); - -// Create a profile and a session -const profile = await client.profiles.create({ name: "my-account" }); -const session = await client.sessions.create({ profileId: profile.id }); -console.log(`Live view: ${session.liveUrl}`); - -// Option A: human logs in via live view -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await new Promise((resolve) => rl.question("Log in and complete 2FA in the live view, then press Enter...", resolve)); -rl.close(); - -// Option B: let the agent log in -// await client.run("Log in to example.com with user@example.com / password123", { sessionId: session.id }); - -// Stop the session — this saves cookies to the profile -await client.sessions.stop(session.id); - -// Next time: reuse the profile, no 2FA needed -const newSession = await client.sessions.create({ profileId: profile.id }); -const result = await client.run("Go to example.com/dashboard and get my balance", { sessionId: newSession.id }); -console.log(result.output); -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. - ---- - -## 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) +uv tool install browser-use +browser-use skill install ``` -```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}`); +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: -// 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); +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. +### 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. -## 3. Agent Mail +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents -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. +[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. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() +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. -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"; +## 1. Create an environment -const client = new BrowserUse(); +Pre-install the CLI so it's ready at session start (no runtime install). -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); +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true ``` -### 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. +## 2. Create a credential vault -### Connect external email via Composio +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). -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. +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | ---- - -## 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`. +## 3. Create the agent -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() +Tell it to use the CLI in cloud mode. -# 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) +```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 ``` -```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"; +## 4. Start a session and send a task -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() +The Console only observes; kick the agent off with a `user.message` event. - Enter the generated code.`, -); -console.log(result.output); +```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."}]}]}' ``` -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? +## 5. Watch it run -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. +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**. + 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 Source: https://docs.browser-use.com/cloud/tutorials/integrations/openclaw @@ -1798,733 +1293,314 @@ 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", - }, -}, - }, -} -``` - -Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: - -- `timeout` — session duration in minutes (max 240) -- `profileId` — load a saved browser profile with persistent cookies and localStorage -- `proxyCountryCode` — route traffic through a specific country (e.g. `us`, `de`, `jp`) - -**3. Use it** - -OpenClaw's browser commands now run against a Browser Use cloud browser: - -```bash -openclaw browser --browser-profile browser-use open https://example.com -openclaw browser --browser-profile browser-use snapshot -openclaw browser --browser-profile browser-use screenshot -``` - -If you set `defaultProfile` to `"browser-use"` in the config (as shown above), you can drop the `--browser-profile` flag: - -```bash -openclaw browser open https://example.com -openclaw browser snapshot -openclaw browser screenshot -``` - -## Option 2: Browser Use CLI - -The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser automation through a SKILL.md file. The agent reads the skill and learns to use the CLI commands directly. It's available on [skills.sh](https://skills.sh/browser-use/browser-use/browser-use) and [ClawHub](https://clawhub.ai/ShawnPana/browser-use). - -### Setup - -**1. Install the CLI** - -```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash -``` - -**2. Verify the installation** - -```bash -browser-use doctor -``` - -**3. Set up the agent** - -Paste this setup prompt into your OpenClaw agent: - -```text -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). - - -# 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", "") + browser: { + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, + }, +} +``` -# 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") +Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: -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() +- `timeout` — session duration in minutes (max 240) +- `profileId` — load a saved browser profile with persistent cookies and localStorage +- `proxyCountryCode` — route traffic through a specific country (e.g. `us`, `de`, `jp`) -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +**3. Use it** -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}") +OpenClaw's browser commands now run against a Browser Use cloud browser: -return {"status": "ok"} +```bash +openclaw browser --browser-profile browser-use open https://example.com +openclaw browser --browser-profile browser-use snapshot +openclaw browser --browser-profile browser-use screenshot ``` - 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. - +If you set `defaultProfile` to `"browser-use"` in the config (as shown above), you can drop the `--browser-profile` flag: -# n8n -Source: https://docs.browser-use.com/cloud/tutorials/integrations/n8n +```bash +openclaw browser open https://example.com +openclaw browser snapshot +openclaw browser screenshot +``` +## Option 2: Browser Use CLI -Browser Use works with [n8n](https://n8n.io) as a standard HTTP integration — no custom nodes needed. +The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser automation through a SKILL.md file. The agent reads the skill and learns to use the CLI commands directly. It's available on [skills.sh](https://skills.sh/browser-use/browser-use/browser-use) and [ClawHub](https://clawhub.ai/ShawnPana/browser-use). -## 1. Create a credential +### Setup -In n8n, go to **Credentials → Add Credential → Header Auth** and set: +**1. Install the CLI** -| Field | Value | -|-------|-------| -| Name | `Authorization` | -| Value | `Bearer YOUR_API_KEY` | +```bash +uv tool install browser-use +``` -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). +**2. Verify the installation** -## 2. Start a session +```bash +browser-use doctor +``` -Add an **HTTP Request** node: +**3. Set up the agent** -| Setting | Value | -|---------|-------| -| Method | `POST` | -| URL | `https://api.browser-use.com/api/v3/sessions` | -| Authentication | Header Auth (from step 1) | -| Body Type | JSON | +Paste this setup prompt into your OpenClaw agent: -Body: -```json -{ - "task": "Find the top 3 trending repos on GitHub today" -} +```text +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. ``` -The response includes a `session_id` you'll use to poll for results. - -## 3. Poll for completion - -Add a second **HTTP Request** node in a loop: +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. -| Setting | Value | -|---------|-------| -| Method | `GET` | -| URL | `https://api.browser-use.com/api/v3/sessions/{{ $json.id }}` | -| Authentication | Header Auth (from step 1) | +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -Check the `status` field. The session is done when status is `idle`, `stopped`, `error`, or `timed_out`. Use an **If** node to loop back with a **Wait** node (5–10 seconds) until complete. +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -The final response contains `output` with the agent's result. -## Event-driven alternative +[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. -Instead of polling, use [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks) to receive a callback when the session completes. Configure your webhook endpoint in the [dashboard](https://cloud.browser-use.com/settings?tab=webhooks), then add a **Webhook** trigger node in n8n to receive `agent.task.status_update` events when sessions finish. +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. - This pattern works with any workflow tool that supports HTTP requests — Make, Zapier, Pipedream, or custom orchestrators. +## Option 1: Cloud Browser Backend +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. -# Chat UI -Source: https://docs.browser-use.com/cloud/tutorials/chat-ui +### Setup +**1. Get your API key** - Clone and run in minutes. Next.js + Browser Use SDK v3. +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). -This tutorial walks through the [chat-ui-example](https://github.com/browser-use/chat-ui-example) — a Next.js app that lets users chat with a Browser Use agent in real time. We focus on the SDK integration, not the UI components. +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -The app has two pages: +**2. Configure Hermes** -1. **Home** — the user types a task, the app creates a session and sends the task. -2. **Session** — live browser preview, streaming messages, follow-ups, and recording download. +Run the setup wizard: -All SDK calls live in a single file: `src/lib/api.ts`. +```bash +hermes setup tools +``` -## Setup +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -```typescript api.ts -import { BrowserUse } from "browser-use-sdk/v3"; +Or configure manually — add your key to `~/.hermes/.env`: -// Server-only — no NEXT_PUBLIC_ prefix, never exposed to the browser -const apiKey = process.env.BROWSER_USE_API_KEY ?? ""; -export const client = new BrowserUse({ apiKey }); +```bash +BROWSER_USE_API_KEY=your_key_here ``` - The API key uses `BROWSER_USE_API_KEY` (no `NEXT_PUBLIC_` prefix) so it stays server-side. All SDK calls go through [server actions](https://nextjs.org/docs/app/guides/forms) — never call the SDK directly from client components. +And set the provider in `~/.hermes/config.yaml`: ---- +```yaml +browser: + cloud_provider: browser-use +``` -## 1. Create a session +**3. Use it** -```typescript actions.ts -"use server"; -import { client } from "./api"; +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: -export async function createSession() { - const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, - }); - return { id: session.id, liveUrl: session.liveUrl, status: session.status }; -} +``` +> Find the top trending repositories on GitHub today and summarize them ``` -- **`keepAlive: true`** keeps the session open after each task so the user can send follow-ups (default is `false`). -- **`enableRecording: true`** produces an MP4 video of the browser session. -- **`liveUrl`** is returned immediately — no waiting or extra call needed. +## Option 2: Browser Use CLI -The home page creates the session, navigates to the session page (passing `liveUrl` and the initial task via URL params), and the session page takes over from there: +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. -```typescript page.tsx -async function handleSend(message: string) { - const session = await createSession(); +### Setup - router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` - ); -} -``` +**1. Install the CLI** ---- +```bash +uv tool install browser-use +``` -## 2. Stream messages with `for await` +**2. Verify the installation** -Instead of polling `sessions.get()` and `sessions.messages()` separately, use `client.run()` — it streams messages and resolves when the task completes: +```bash +browser-use doctor +``` -```typescript session-context.tsx -const streamTask = useCallback(async (task: string) => { - const run = client.run(task, { sessionId }); +**3. Register the skill** - for await (const msg of run) { -setMessages((prev) => [...prev, msg]); - } +Register the Browser Use skill with the installed CLI: - // Iterator done — task reached terminal state - setSession(run.result); -}, [sessionId]); +```bash +browser-use skill install ``` -The `for await` loop yields each message as it arrives. When the loop ends, `run.result` contains the final session state (status, output, etc.). No separate status polling needed. +Or ask Hermes directly in chat to install it. + +**4. Authenticate for cloud browsers** -Wire it up in a `useEffect` to auto-run the initial task from URL params: +Authenticate with your API key: -```typescript session-context.tsx -useEffect(() => { - if (!initialTask) return; - sendMessage(initialTask); -}, []); +```bash +browser-use auth login ``` ---- +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. -## 3. Follow-up tasks +**5. Use it** -Follow-ups call the same `streamTask` function — the stream already includes the user message, so no optimistic insert is needed: +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: -```typescript session-context.tsx -const sendMessage = useCallback(async (task: string) => { - await streamTask(task); -}, [streamTask]); +``` +> Use browser-use to open github.com/trending and summarize the top repos ``` -The SDK auto-sets `keepAlive: true` when targeting an existing session, so follow-up tasks work without extra config. - ---- +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). -## 4. Recording +## Agent Self-Registration -Fetch the MP4 URL after the session ends (recording was enabled in step 1): +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. -```typescript session-context.tsx -useEffect(() => { - if (!isTerminal) return; +Install the Browser Use CLI and skill: - client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); - }); -}, [isTerminal, sessionId]); +```bash +uv tool install browser-use +browser-use skill install ``` -`waitForRecording` polls for up to 15 seconds and returns presigned MP4 download URLs. Returns an empty array if the agent answered without opening a browser. +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** -## 5. Stop a task +For the cloud browser backend (Option 1): -```typescript actions.ts -export async function stopTask(id: string) { - await client.sessions.stop(id, { strategy: "task" }); -} +```bash +hermes config set BROWSER_USE_API_KEY ``` -Using `strategy: "task"` stops only the current task, keeping the session alive for follow-ups. - ---- +For CLI mode (Option 2), put the key in the agent's shell environment: -## 6. Session page - -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(); - - return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
- ); -} +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status ``` ---- +### Claim the account (optional) -## Summary +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. -| Method | Purpose | -|--------|---------| -| `client.sessions.create()` | Create a session (returns `liveUrl` immediately) | -| `client.run()` | Send a task and stream messages with `for await` | -| `client.sessions.stop()` | Stop the current task | -| `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup -# Grow Therapy provider search -Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare +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. -This tutorial builds a provider search tool for [Grow Therapy](https://www.growtherapy.com) — a therapy marketplace that handles insurance credentialing for providers. We combine [structured output](https://docs.browser-use.com/cloud/agent/structured-output) with [deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script) to build a fast, repeatable search pipeline. +## REST flow -## What you'll build +### 1. Request a challenge -A script that: -1. Searches Grow Therapy's provider directory with filters (location, insurance, specialty) -2. Extracts therapist profiles with ratings and availability -3. Caches the search so you can sweep across geographies or specialties instantly +```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): -## Setup +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` -```python Python -import asyncio -import json -from pydantic import BaseModel -from browser_use_sdk.v3 import AsyncBrowserUse +Response: -client = AsyncBrowserUse() +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} ``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { z } from "zod"; -const client = new BrowserUse(); -``` +### 2. Solve the challenge -## 1. Define the output schema +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. -```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 - -class ProviderSearch(BaseModel): -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(), - })), - totalFound: z.number().nullable(), - location: z.string(), - specialty: z.string(), -}); +### 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"}' ``` -## 2. Create a workspace +Request body: -```python Python -workspace = await client.workspaces.create(name="grow-therapy-search") +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} ``` -```typescript TypeScript -const workspace = await client.workspaces.create({ name: "grow-therapy-search" }); + +Response: + +```json +{ + "api_key": "bu_..." +} ``` -## 3. Search for providers +Use the returned key for Browser Use Cloud API requests. -```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, -) +For example, create an API V4 run: -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() +```bash +curl https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{"task":"Find the top Hacker News story"}' ``` -```typescript TypeScript -const 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.", - { workspaceId: workspace.id, schema: ProviderSearch }, -); -for (const p of result.output.providers) { - console.log(`${p.name} (${p.title})`); - console.log(` Specialties: ${p.specialties.join(", ")}`); - console.log(` Rating: ${p.rating}`); - console.log(` Next available: ${p.nextAvailable}`); -} -``` +See the [API V4 quick start](https://docs.browser-use.com/cloud/agent/quickstart). -## 4. Sweep across locations and specialties +## Claim the account -After the first run caches the search flow, sweep across different parameters at $0 LLM cost: +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: -```python Python -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") +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." ``` -```typescript TypeScript -const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; -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`); - } +Response: + +```json +{ + "claim_url": "https://..." } ``` ---- - -## Summary - -| Step | What happens | Cost | -|------|-------------|------| -| First search | Agent navigates Grow Therapy, caches the flow | ~$0.10 | -| 12 cached sweeps (4 cities x 3 specialties) | Script reruns with new params | **$0 LLM each** | -| Site layout change | [Auto-healing](https://docs.browser-use.com/cloud/agent/cache-script#auto-healing) regenerates the script | ~$0.10 | +The claim URL is valid for 1 hour. -Therapy platforms have dynamic UIs that can change frequently. [Auto-healing](https://docs.browser-use.com/cloud/agent/cache-script#auto-healing) ensures your cached scripts stay working without manual maintenance. +## CLI usage -## Next steps +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: -- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output) — Learn more about extracting typed data with Pydantic and Zod schemas. -- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) — Let a human review or interact with the browser mid-task, useful for auth flows or approving results before continuing. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script) — Deep dive into how caching and auto-healing work. +```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. # FAQ Source: https://docs.browser-use.com/cloud/faq @@ -2532,19 +1608,30 @@ Source: https://docs.browser-use.com/cloud/faq ## Which model should I use? -- **Claude Opus 4.6** (`claude-opus-4.6`) — most capable. Use for the hardest tasks that need maximum accuracy. -- **Claude Sonnet 4.6** (`claude-sonnet-4.6`, default) — best balance of capability and cost. Use for complex multi-step workflows. -- **GPT-5.4 mini** (`gpt-5.4-mini`) — fast and efficient. Good for simple, well-defined tasks. +- **Claude Opus 5** (`claude-opus-5`) — maximum intelligence for difficult, long-horizon work. +- **GPT-5.6** (`gpt-5.6`) — fast on complex tasks. +- **Gemini 3.5 Flash** (`gemini-3.5-flash`) — fast for simpler tasks. +- **MiniMax M3** (`minimax-m3`, default) — cheapest for simple and high-volume tasks. + +See [Models](https://docs.browser-use.com/cloud/agent/models) for the complete V4 picker and pricing. ## How do I get the live browser URL? -`live_url` is returned on session creation. Embed it in an iframe or open it in a browser. +The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -session = await client.sessions.create(task="Go to example.com") -print(session.live_url) +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +created = client.runs.create("Go to example.com") +client.runs.wait_for_completion(created.id) +events = client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +print(ready.data["live_view_url"]) ``` +Poll events until `browser.ready` appears if you need the URL while the run is still active. See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for a complete flow. + ## Getting blocked by a website Stealth and proxies are active by default. If you're still getting blocked: @@ -2558,25 +1645,21 @@ If it still doesn't work, contact support inside the [Cloud Dashboard](https://c The SDK auto-retries 429 responses with exponential backoff. If persistent, you may need more concurrent sessions — contact support. -## v2 vs v3 — which should I use? - -**v3 is the recommendation for everything.** It's a premium agent (not available in open source) that is significantly more capable than v2: +## V2 or V4 — which should I use? -- **Much better at complex tasks** and multi-step workflows -- **Much better at large data extraction** -- **File system** with persistent memory across tasks -- **Task scheduling** with 1,000+ integrations (Gmail, Slack, and more) +Use **V4** for difficult tasks where accuracy matters. It supports: -v2 is the closest to the open-source experience — pure browser automation, nothing else. If the open source already works great for your use case, v2 is the natural fit. For everything else, use v3. +- Run-focused API with a cheap status polling endpoint +- Conversation sessions with follow-ups +- Persistent workspaces and turn-scoped file attachments +- Incremental events for custom UIs and monitoring +- Per-run cost totals, cost caps, and optional judgement -```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse - -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse -``` +Use **V2** when tasks are simple and your priority is very low cost and +predictable speed. Its accuracy is substantially lower. +See Browser Use at #1 on the +[Odysseys benchmark](https://odysseysbench.com/leaderboard). # Agent (v2) Source: https://docs.browser-use.com/cloud/legacy/agent @@ -2587,7 +1670,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 | @@ -2621,15 +1703,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) ``` @@ -2662,8 +1744,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); @@ -2685,8 +1767,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 ``` @@ -2727,7 +1809,6 @@ console.log(run.result?.output); // final result after iteration | `op_vault_id` | `str` | 1Password vault ID for auto-fill credentials and 2FA. | | `metadata` | `dict[str, str]` | Custom metadata attached to the task. | - # Public share links (v2) Source: https://docs.browser-use.com/cloud/legacy/public-share @@ -2743,7 +1824,6 @@ const share = await client.sessions.createShare(session.id); console.log(share.shareUrl); ``` - # Skills Source: https://docs.browser-use.com/cloud/legacy/skills @@ -2767,8 +1847,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) ``` @@ -2789,8 +1869,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) ``` @@ -2831,7 +1911,6 @@ const result = await client.marketplace.execute(skillId, { parameters: { ... } } See [Pricing](https://browser-use.com/pricing) for skill costs. - # 1Password & 2FA Source: https://docs.browser-use.com/cloud/guides/1password @@ -2862,9 +1941,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) ``` @@ -2875,8 +1954,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); @@ -2886,17 +1965,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"], }, ); ``` @@ -2912,7 +1991,6 @@ When the agent encounters a login form: The agent never sees your actual credentials. The actual username, password, and 2FA codes are filled in programmatically — keeping your secrets hidden from the AI model. - # Secrets Source: https://docs.browser-use.com/cloud/guides/secrets @@ -2924,9 +2002,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 @@ -2936,8 +2014,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"], }, ); ``` @@ -2948,30 +2026,29 @@ 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-reference +Source: https://docs.browser-use.com/cloud/api-v4-overview ## Authentication @@ -2987,42 +2064,44 @@ Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/sett ## Base URL ``` -https://api.browser-use.com/api/v3 +https://api.browser-use.com/api/v4 ``` -## Quick example +## The core loop -```bash Create a session -curl -X POST https://api.browser-use.com/api/v3/sessions \ +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 Get session result (replace SESSION_ID) -curl https://api.browser-use.com/api/v3/sessions/SESSION_ID \ +```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" ``` -## Environment variable - -Set the key once so SDKs pick it up automatically: - -```bash -export 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 -Prefer the SDK? See the [Agent docs](https://docs.browser-use.com/cloud/agent/quickstart) — the SDK has all API endpoints available as methods, including `client.browsers.create()`. +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 Python -pip install browser-use-sdk -``` -```bash TypeScript -npm install browser-use-sdk +```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 quick start](https://docs.browser-use.com/cloud/agent/quickstart) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python. # API key Source: https://docs.browser-use.com/cloud/api-v2-overview @@ -3046,66 +2125,3 @@ pip install browser-use-sdk ```bash TypeScript 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 8b1c30c10..3a2b3937e 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,15 +1,35 @@ -# Browser Use Cloud SDK +# Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task, get structured results back. SDKs for Python and TypeScript. Always use API v3 — v2 is legacy and uses different method names. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud has two products on the same managed browser +> infrastructure. **Agent** accepts a natural-language goal and completes the +> web task. **Browser** gives Playwright, Puppeteer, and other remote CDP +> clients direct control of a cloud browser. Both include stealth, residential +> proxies, profiles, and live observability. Auth uses +> `X-Browser-Use-API-Key` (keys start with `bu_`). - 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 -- 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. +- OpenAPI spec (v4): https://docs.browser-use.com/cloud/openapi/v4.json - 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. -**Always use v3.** v2 is legacy with different method names and should not be used for new projects. +**Choose API V4 for hard, high-accuracy tasks.** It is the recommended Agent API for new integrations and works especially well for long, complex workflows. + +**Choose API V2 for simple tasks when extremely low cost or predictable speed matters more than accuracy.** V2 accuracy is substantially lower than V4. + +Browser Use ranks #1 on the [Odysseys benchmark](https://odysseysbench.com/leaderboard). Use the benchmark when accuracy is the deciding factor. + +**Stopping standalone browsers:** Do not use `client.close()`, +`browser.close()`, or a dropped CDP connection as the API V4 stop operation. +Keep the browser session ID returned by `POST /api/v4/browsers`, then call +`PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and +refunds unused browser time. + +**Current TypeScript SDK typing:** Pass `model` explicitly (use `grok-4.5` for +the best price/accuracy balance). Whenever `browserSettings` is present, also +pass `proxyCountryCode`: use `"us"` to keep the default or `null` to disable +the managed proxy. New model strings can reach REST before the generated +TypeScript union; use `POST /api/v4/runs` directly if a listed model is rejected. Before writing code, check if `browser-use-sdk` is already installed. If so, upgrade to the latest version. If not, install it: - Python: `pip install --upgrade browser-use-sdk` @@ -22,43 +42,44 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [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. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a hosted agent or launch a cloud browser. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent -- [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. -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Run multiple tasks in the same browser session. -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Stream the agent's messages in real time to build custom UIs or monitor progress. -- [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. +- [Run a task](https://docs.browser-use.com/cloud/agent/quickstart): Give a high-accuracy browser agent a goal and get the result. +- [Models](https://docs.browser-use.com/cloud/agent/models): Choose a V4 model and understand its token pricing. +- [Structured output](https://docs.browser-use.com/cloud/agent/structured-output): Ask for JSON and validate the V4 result in your application. +- [Sessions](https://docs.browser-use.com/cloud/agent/sessions): Continue one conversation across multiple V4 runs. +- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Persist files across V4 runs and conversations. +- [Scripts](https://docs.browser-use.com/cloud/agent/scripts): Save tested browser scripts in a workspace and reuse them on later runs. +- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the live browser, take over, then continue the same session. +- [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser -- [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. -- [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Residential proxies in 195+ countries. On by default. -- [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. +- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a cloud browser and connect to it from your code. +- [Stealth](https://docs.browser-use.com/cloud/browser/stealth): Best stealth on the planet. We fork Chromium to give agents access to all websites. +- [Proxies](https://docs.browser-use.com/cloud/browser/proxies): Route API V4 agent runs through residential or custom proxies. +- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview): Watch an API V4 run in real time or record its browser. +- [Playwright, Puppeteer, Selenium](https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium): Control a Browser Use cloud browser directly over 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. -- [2FA](https://docs.browser-use.com/cloud/guides/2fa): Best practices for handling two-factor authentication in automated browser sessions. +- [Profiles](https://docs.browser-use.com/cloud/guides/authentication): Reuse cookies and browser state in API V4 runs. +- [Sync local and cloud cookies](https://docs.browser-use.com/cloud/guides/profile-sync): Sync a local login, then use it in an API V4 run. +- [2FA](https://docs.browser-use.com/cloud/guides/2fa): Handle two-factor authentication in API V4 runs. ## 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. -- [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. -- [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. + +## Anthropic +- [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. ## 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. -- [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. +- [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. ## Legacy (v2) - [Agent (v2)](https://docs.browser-use.com/cloud/legacy/agent): V2 agent models and file handling. @@ -67,70 +88,8 @@ 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 v3 -- [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. +## 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 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/openapi/v4.json b/docs/openapi/v4.json index 0a15025f7..ec1f6424c 100644 --- a/docs/openapi/v4.json +++ b/docs/openapi/v4.json @@ -3105,9 +3105,12 @@ "enum": [ "glm-5.2", "grok-4.5", + "kimi-k3", "minimax-m3", "claude-opus-4.7", "claude-opus-4.8", + "claude-opus-5", + "claude-fable-5", "claude-sonnet-5", "gpt-5.5", "gpt-5.6", @@ -3751,7 +3754,7 @@ "anyOf": [ { "type": "string", - "maxLength": 255 + "maxLength": 100 }, { "type": "null"