From 262a4345ae108bff8a00007e1821a3e65b7e5ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 16:15:25 -0700 Subject: [PATCH 01/15] docs: migrate agent guides to API v4 --- browser-use-node/src/generated/v4/types.ts | 7 +- browser-use-node/src/v4.ts | 8 +- browser-use-node/src/v4/resources/runs.ts | 6 +- browser-use-node/src/v4/resources/sessions.ts | 5 + browser-use-node/tests/v4.test.ts | 31 + browser-use-node/tests/vibe.test.ts | 1 + .../browser_use_sdk/generated/v4/models.py | 10 +- .../browser_use_sdk/v4/resources/sessions.py | 8 + browser-use-python/tests/test_v4.py | 9 + browser-use-python/tests/test_vibe.py | 1 + docs/cloud/agent/cache-script.mdx | 297 +- docs/cloud/agent/follow-up-tasks.mdx | 74 +- docs/cloud/agent/human-in-the-loop.mdx | 103 +- docs/cloud/agent/models.mdx | 88 +- docs/cloud/agent/quickstart.mdx | 46 +- docs/cloud/agent/streaming.mdx | 147 +- docs/cloud/agent/structured-output.mdx | 58 +- docs/cloud/agent/workspaces.mdx | 220 +- docs/cloud/api-v4-overview.mdx | 2 +- docs/cloud/faq.mdx | 43 +- docs/cloud/llms-full.txt | 2575 ++++++++++------- docs/cloud/llms.txt | 96 +- docs/cloud/openapi/v4.json | 5 +- docs/cloud/quickstart.mdx | 32 +- docs/docs.json | 6 +- docs/generate-llms-txt.sh | 7 +- docs/llms-full.txt | 2575 ++++++++++------- docs/llms.txt | 96 +- docs/openapi/v4.json | 5 +- snapshots/v4.json | 3 +- 30 files changed, 3469 insertions(+), 3095 deletions(-) diff --git a/browser-use-node/src/generated/v4/types.ts b/browser-use-node/src/generated/v4/types.ts index 250eeea8..5a896eb2 100644 --- a/browser-use-node/src/generated/v4/types.ts +++ b/browser-use-node/src/generated/v4/types.ts @@ -1110,12 +1110,7 @@ export interface components { * @default minimax-m3 * @enum {string} */ - // POST-GEN PATCH: "kimi-k3" and "claude-fable-5" omitted on purpose — - // live in the API but not public yet. openapi-typescript re-adds them - // from the spec on every regen, so re-apply this after `task gen:types` - // until the backend advertises a public model subset. (The API still - // accepts them; this only hides them from the SDK type.) - model: "glm-5.2" | "grok-4.5" | "minimax-m3" | "claude-opus-4.7" | "claude-opus-4.8" | "claude-sonnet-5" | "gpt-5.5" | "gpt-5.6" | "gemini-3.5-flash" | "gemini-3.1-pro" | "gemini-3-flash"; + model: "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" | "gemini-3.5-flash" | "gemini-3.1-pro" | "gemini-3-flash"; /** Sessionid */ sessionId?: string | null; /** Workspaceid */ diff --git a/browser-use-node/src/v4.ts b/browser-use-node/src/v4.ts index 80f9d4e9..c5a163e6 100644 --- a/browser-use-node/src/v4.ts +++ b/browser-use-node/src/v4.ts @@ -4,7 +4,12 @@ export type { BrowserUseOptions } from "./v4/client.js"; export { BrowserUseError } from "./core/errors.js"; export { Runs } from "./v4/resources/runs.js"; -export type { RunListParams, RunEventsParams, WaitOptions } from "./v4/resources/runs.js"; +export type { + RunCreateRequest, + RunListParams, + RunEventsParams, + WaitOptions, +} from "./v4/resources/runs.js"; export { Sessions } from "./v4/resources/sessions.js"; export type { SessionListParams } from "./v4/resources/sessions.js"; @@ -19,7 +24,6 @@ import type { components } from "./generated/v4/types.js"; type S = components["schemas"]; // Run models -export type RunCreateRequest = S["RunCreateRequest"]; export type RunCreateResponse = S["RunCreateResponse"]; export type RunSummary = S["RunSummary"]; export type RunStatusResponse = S["RunStatusResponse"]; diff --git a/browser-use-node/src/v4/resources/runs.ts b/browser-use-node/src/v4/resources/runs.ts index 767ff404..bdbdb558 100644 --- a/browser-use-node/src/v4/resources/runs.ts +++ b/browser-use-node/src/v4/resources/runs.ts @@ -1,7 +1,11 @@ import type { HttpClient } from "../../core/http.js"; import type { components } from "../../generated/v4/types.js"; -type RunCreateRequest = components["schemas"]["RunCreateRequest"]; +type GeneratedRunCreateRequest = components["schemas"]["RunCreateRequest"]; +export type RunCreateRequest = Omit & { + /** Defaults to minimax-m3 when omitted. */ + model?: GeneratedRunCreateRequest["model"]; +}; type RunCreateResponse = components["schemas"]["RunCreateResponse"]; type RunSummary = components["schemas"]["RunSummary"]; type RunStatusResponse = components["schemas"]["RunStatusResponse"]; diff --git a/browser-use-node/src/v4/resources/sessions.ts b/browser-use-node/src/v4/resources/sessions.ts index 487c7ef2..7185b026 100644 --- a/browser-use-node/src/v4/resources/sessions.ts +++ b/browser-use-node/src/v4/resources/sessions.ts @@ -25,6 +25,11 @@ export class Sessions { return this.http.get(`/sessions/${sessionId}`); } + /** Immediately purge all data for a session. Available to ZDR projects only. */ + purge(sessionId: string): Promise { + return this.http.post(`/sessions/${sessionId}/purge`); + } + /** * Send a message to the session. Runs as the next turn when the session is * busy; set `interrupt: true` to cancel the active run so the message runs diff --git a/browser-use-node/tests/v4.test.ts b/browser-use-node/tests/v4.test.ts index a7a730e0..7f104ffb 100644 --- a/browser-use-node/tests/v4.test.ts +++ b/browser-use-node/tests/v4.test.ts @@ -6,6 +6,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { Runs } from "../src/v4/resources/runs.js"; import { Sessions } from "../src/v4/resources/sessions.js"; import { Workspaces } from "../src/v4/resources/workspaces.js"; +import type { RunCreateRequest } from "../src/v4.js"; const RUN_ID = "00000000-0000-0000-0000-000000000001"; const SESSION_ID = "00000000-0000-0000-0000-000000000002"; @@ -32,6 +33,25 @@ function runSummary(status: string) { } describe("v4 runs.waitForCompletion", () => { + it("creates a run without requiring the API-defaulted model", async () => { + const http = { + post: vi.fn(async () => ({ + id: RUN_ID, + sessionId: SESSION_ID, + workspaceId: WORKSPACE_ID, + status: "queued", + })), + }; + const runs = new Runs(http as any); + const request: RunCreateRequest = { task: "Find the top HN post" }; + + await runs.create(request); + + expect(http.post).toHaveBeenCalledWith("/runs", { + task: "Find the top HN post", + }); + }); + it("polls status until terminal, then fetches the full run once", async () => { const statuses = ["queued", "running", "completed"]; let statusCalls = 0; @@ -163,6 +183,17 @@ describe("v4 sessions queue", () => { expect(msg.status).toBe("pending"); }); + it("purges a session through the ZDR endpoint", async () => { + const http = { + post: vi.fn(async () => undefined), + }; + const sessions = new Sessions(http as any); + + await sessions.purge(SESSION_ID); + + expect(http.post).toHaveBeenCalledWith(`/sessions/${SESSION_ID}/purge`); + }); + it("lists pending queued messages", async () => { const http = { get: vi.fn(async () => ({ queue: [queuedMessage] })), diff --git a/browser-use-node/tests/vibe.test.ts b/browser-use-node/tests/vibe.test.ts index 4891b06b..883aa1bb 100644 --- a/browser-use-node/tests/vibe.test.ts +++ b/browser-use-node/tests/vibe.test.ts @@ -179,6 +179,7 @@ function v4EndpointToSdkMethod( // Sessions + queue if (method === "get" && path === "/sessions") return { resource: "sessions", method: "list" }; if (method === "get" && path === "/sessions/{session_id}") return { resource: "sessions", method: "get" }; + if (method === "post" && path === "/sessions/{session_id}/purge") return { resource: "sessions", method: "purge" }; if (method === "post" && path === "/sessions/{session_id}/queue") return { resource: "sessions", method: "sendMessage" }; if (method === "get" && path === "/sessions/{session_id}/queue") return { resource: "sessions", method: "queue" }; if (method === "delete" && path === "/sessions/{session_id}/queue/{message_id}") return { resource: "sessions", method: "removeMessage" }; diff --git a/browser-use-python/src/browser_use_sdk/generated/v4/models.py b/browser-use-python/src/browser_use_sdk/generated/v4/models.py index d6699071..a00a2048 100644 --- a/browser-use-python/src/browser_use_sdk/generated/v4/models.py +++ b/browser-use-python/src/browser_use_sdk/generated/v4/models.py @@ -657,17 +657,15 @@ class RunBrowserSettings(BaseModel): ) -# POST-GEN PATCH: kimi-k3 and claude-fable-5 are omitted on purpose — they are -# live in the API but not public yet. datamodel-codegen re-adds them from the -# spec on every regen, so this must be re-applied after `task gen:types` until -# the backend advertises a public model subset. (The API still accepts them; -# this only hides them from the SDK enum.) class Model(Enum): glm_5_2 = 'glm-5.2' grok_4_5 = 'grok-4.5' + kimi_k3 = 'kimi-k3' minimax_m3 = 'minimax-m3' claude_opus_4_7 = 'claude-opus-4.7' claude_opus_4_8 = 'claude-opus-4.8' + claude_opus_5 = 'claude-opus-5' + claude_fable_5 = 'claude-fable-5' claude_sonnet_5 = 'claude-sonnet-5' gpt_5_5 = 'gpt-5.5' gpt_5_6 = 'gpt-5.6' @@ -812,7 +810,7 @@ class ValidationError(BaseModel): class Name2(RootModel[str]): - root: str = Field(..., max_length=255, title='Name') + root: str = Field(..., max_length=100, title='Name') class WorkspaceCreateRequest(BaseModel): diff --git a/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py b/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py index 440a6976..392245a1 100644 --- a/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py +++ b/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py @@ -57,6 +57,10 @@ def get(self, session_id: str | UUID) -> SessionInfo: self._http.request("GET", f"/sessions/{session_id}") ) + def purge(self, session_id: str | UUID) -> None: + """Immediately purge all session data. Available to ZDR projects only.""" + self._http.request("POST", f"/sessions/{session_id}/purge") + def send_message( self, session_id: str | UUID, @@ -120,6 +124,10 @@ async def get(self, session_id: str | UUID) -> SessionInfo: await self._http.request("GET", f"/sessions/{session_id}") ) + async def purge(self, session_id: str | UUID) -> None: + """Immediately purge all session data. Available to ZDR projects only.""" + await self._http.request("POST", f"/sessions/{session_id}/purge") + async def send_message( self, session_id: str | UUID, diff --git a/browser-use-python/tests/test_v4.py b/browser-use-python/tests/test_v4.py index 1f271768..0082bbf8 100644 --- a/browser-use-python/tests/test_v4.py +++ b/browser-use-python/tests/test_v4.py @@ -264,6 +264,15 @@ def test_sessions_send_message() -> None: assert msg.status.value == "pending" +def test_sessions_purge() -> None: + http = FakeSyncHttp([{}]) + sessions = Sessions(http) # type: ignore[arg-type] + + sessions.purge(SESSION_ID) + + assert http.calls[0][:2] == ("POST", f"/sessions/{SESSION_ID}/purge") + + def test_sessions_queue_list() -> None: http = FakeSyncHttp([{"queue": [_queued_message()]}]) sessions = Sessions(http) # type: ignore[arg-type] diff --git a/browser-use-python/tests/test_vibe.py b/browser-use-python/tests/test_vibe.py index 8b8df3e6..f95b5750 100644 --- a/browser-use-python/tests/test_vibe.py +++ b/browser-use-python/tests/test_vibe.py @@ -144,6 +144,7 @@ def _load_spec(path: Path) -> Dict[str, Any]: # sessions + queue ("get", "/sessions"): ("sessions", "list"), ("get", "/sessions/{session_id}"): ("sessions", "get"), + ("post", "/sessions/{session_id}/purge"): ("sessions", "purge"), ("post", "/sessions/{session_id}/queue"): ("sessions", "send_message"), ("get", "/sessions/{session_id}/queue"): ("sessions", "queue"), ("delete", "/sessions/{session_id}/queue/{message_id}"): ("sessions", "remove_message"), diff --git a/docs/cloud/agent/cache-script.mdx b/docs/cloud/agent/cache-script.mdx index 6f850920..825f109e 100644 --- a/docs/cloud/agent/cache-script.mdx +++ b/docs/cloud/agent/cache-script.mdx @@ -1,280 +1,75 @@ --- title: Deterministic rerun -description: "Run a task once, then re-execute it for $0 LLM cost." +description: "Have the agent save and test a reusable script, then run it again from the same workspace." 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. +For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. -## 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. +You can create the workspace in the dashboard or through the API: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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), +workspace = await client.workspaces.create(name="hn-scraper") + +created = await client.runs.create( + """ + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + """, + workspace_id=workspace.id, ) +first = await client.runs.wait_for_completion(created.id) +print(first.result) ``` ```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-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 }, -); +const workspace = await client.workspaces.create({ name: "hn-scraper" }); + +const created = await client.runs.create({ + task: ` + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + `, + workspaceId: workspace.id, +}); +const first = await client.runs.waitForCompletion(created.id); +console.log(first.result); ``` -### Multiple parameters +Later, start a new run in the same workspace and tell the agent to use the saved script: ```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), +created = await client.runs.create( + "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspace_id=workspace.id, ) +rerun = await client.runs.wait_for_completion(created.id) +print(rerun.result) ``` ```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)`); -} +const created = await client.runs.create({ + task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspaceId: workspace.id, +}); +const rerun = await client.runs.waitForCompletion(created.id); +console.log(rerun.result); ``` -## 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 | +This pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. - -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. - + + V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. + diff --git a/docs/cloud/agent/follow-up-tasks.mdx b/docs/cloud/agent/follow-up-tasks.mdx index e95fd049..6686dd18 100644 --- a/docs/cloud/agent/follow-up-tasks.mdx +++ b/docs/cloud/agent/follow-up-tasks.mdx @@ -1,52 +1,74 @@ --- title: Follow-up tasks -description: "Run multiple tasks in the same browser session." +description: "Continue the same V4 conversation, workspace, and browser." 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. +Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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, +first = await client.runs.create( + "Go to amazon.com, search for laptops, and open the first result" ) -result2 = await client.run( +first_result = await client.runs.wait_for_completion(first.id) + +follow_up = await client.runs.create( "Extract the customer reviews", - session_id=session.id, + session_id=first.session_id, ) - -await client.sessions.stop(session.id) +follow_up_result = await client.runs.wait_for_completion(follow_up.id) +print(follow_up_result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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 first = await client.runs.create({ + task: "Go to amazon.com, search for laptops, and open the first result", }); -const result2 = await client.run("Extract the customer reviews", { - sessionId: session.id, +await client.runs.waitForCompletion(first.id); + +const followUp = await client.runs.create({ + task: "Extract the customer reviews", + sessionId: first.sessionId, }); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); +``` + + +The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. + +There is no separate empty-session creation step in V4: -await client.sessions.stop(session.id); +- Omit `session_id` / `sessionId` to create a new session implicitly. +- Pass a previous session ID to continue it explicitly. +- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. + +## Queue a follow-up + +Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. + + +```python Python +queued = await client.sessions.send_message( + first.session_id, + "Also compare the warranty options", +) +``` +```typescript TypeScript +const queued = await client.sessions.sendMessage(first.sessionId, { + text: "Also compare the warranty options", +}); ``` -`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). +Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](/cloud/api-v4/sessions/get-session) or [List runs](/cloud/api-v4/runs/list-runs) to discover the new run once it starts. - - Sessions time out after 15 minutes of inactivity by default. The maximum session duration is 4 hours. - +See [Queue session message](/cloud/api-v4/sessions/queue-session-message) for the full request shape. diff --git a/docs/cloud/agent/human-in-the-loop.mdx b/docs/cloud/agent/human-in-the-loop.mdx index 3ed8a2ae..7ef65d43 100644 --- a/docs/cloud/agent/human-in-the-loop.mdx +++ b/docs/cloud/agent/human-in-the-loop.mdx @@ -1,88 +1,69 @@ --- 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 V4 live browser, let a person 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 +Use a human checkpoint for approvals, payments, complex authentication, or reviewing work 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 +The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() - -# 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, +created = await client.runs.create( + "Find noise-cancelling headphones on Amazon and stop before selecting a product" ) -print(result.output) +await client.runs.wait_for_completion(created.id) -# 3. Human opens live_url and picks a product -input("Press Enter after you've selected a product in the live view...") +events = await client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +live_url = ready.data["live_view_url"] +print(f"Open this live browser: {live_url}") -# 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) +input("Press Enter after selecting a product...") -# Clean up -await client.sessions.stop(session.id) +follow_up = await client.runs.create( + "Get the selected product's name, price, and rating", + session_id=created.session_id, +) +result = await client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; +import { BrowserUse } from "browser-use-sdk/v4"; +import * as readline from "node:readline/promises"; const client = new BrowserUse(); +const created = await client.runs.create({ + task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +}); +await client.runs.waitForCompletion(created.id); -// 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); +const events = await client.runs.events(created.id, { limit: 100 }); +const ready = events.events.find((event) => event.type === "browser.ready"); +const liveUrl = ready?.data.live_view_url; +console.log(`Open this live browser: ${liveUrl}`); -// 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), -); +await rl.question("Press Enter after selecting a product..."); 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); +const followUp = await client.runs.create({ + task: "Get the selected product's name, price, and rating", + sessionId: created.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); ``` +The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. + + + Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. + + +See [Get run events](/cloud/api-v4/runs/get-run-events) for the event response. diff --git a/docs/cloud/agent/models.mdx b/docs/cloud/agent/models.mdx index da9dc289..ca184884 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -1,88 +1,60 @@ --- 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 `model` when you create a run. These are the models currently shown in the V4 agent UI: -| 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 | Bring your own key | +| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | — | + +Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run( - "List the top 20 posts on Hacker News today with their points", - model="claude-sonnet-4.6", +created = await client.runs.create( + "Compare the top three project-management tools for a 20-person startup", + model="claude-opus-5", ) -print(result.output) +run = await client.runs.wait_for_completion(created.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", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const created = await client.runs.create({ + task: "Compare the top three project-management tools for a 20-person startup", + model: "claude-opus-5", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -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"}' + -d '{"task": "Compare the top three project-management tools", "model": "claude-opus-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: +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. - -```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 }); -``` - +With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. -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. +Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. diff --git a/docs/cloud/agent/quickstart.mdx b/docs/cloud/agent/quickstart.mdx index 5c170dd0..995b641d 100644 --- a/docs/cloud/agent/quickstart.mdx +++ b/docs/cloud/agent/quickstart.mdx @@ -1,46 +1,46 @@ --- 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." +description: "Run a long-horizon browser agent with one task and a few lines of code." 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`. - -`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. +The SDK wraps the [API v4 Reference](/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +created = await client.runs.create("List the top 20 Hacker News posts and their points") +run = await client.runs.wait_for_completion(created.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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task": "List the top 20 Hacker News posts and their points"}' ``` -**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 +`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](/cloud/api-v4/runs/get-run-status), then fetches the full [run result](/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. + +Use the agent for: -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +- Data extraction and research across many pages +- Form filling, downloads, and multi-step workflows +- Authenticated work with browser profiles +- Long-running tasks that create or consume files +- Follow-up turns that preserve the same conversation, workspace, and live browser -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. +See [Follow-up tasks](/cloud/agent/follow-up-tasks), [Live messages](/cloud/agent/streaming), and [Workspaces & files](/cloud/agent/workspaces) for the main V4 patterns. diff --git a/docs/cloud/agent/streaming.mdx b/docs/cloud/agent/streaming.mdx index 5fa5520e..3a850b07 100644 --- a/docs/cloud/agent/streaming.mdx +++ b/docs/cloud/agent/streaming.mdx @@ -1,132 +1,85 @@ --- title: Live messages -description: "Stream the agent's messages in real time to build custom UIs or monitor progress." +description: "Poll V4 run events incrementally to monitor progress or build a custom UI." icon: message-lines --- - - Want a ready-made UI? See the [Chat UI tutorial](/cloud/tutorials/chat-ui). - +V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. -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. +Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +import asyncio +from browser_use_sdk.v4 import AsyncBrowserUse + +TERMINAL = {"completed", "failed", "cancelled"} client = AsyncBrowserUse() +created = await client.runs.create("Find the top story on Hacker News") -run = client.run("Find the top story on Hacker News") -async for msg in run: - print(f"[{msg.role}] {msg.summary}") +after = None +while True: + page = await client.runs.events(created.id, after=after, limit=100) + for event in page.events: + print(event.type, event.data) + if page.next_after is not None: + after = page.next_after + + status = await client.runs.status(created.id) + if status.status.value in TERMINAL: + break + await asyncio.sleep(1) -print(run.result.output) +run = await client.runs.get(created.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; +const TERMINAL = new Set(["completed", "failed", "cancelled"]); const client = new BrowserUse(); +const created = await client.runs.create({ + task: "Find the top story on Hacker News", +}); -const run = client.run("Find the top story on Hacker News"); -for await (const msg of run) { - console.log(`[${msg.role}] ${msg.summary}`); +let after: number | undefined; +while (true) { + const page = await client.runs.events(created.id, { after, limit: 100 }); + for (const event of page.events) { + console.log(event.type, event.data); + } + if (page.nextAfter != null) after = page.nextAfter; + + const { status } = await client.runs.status(created.id); + if (TERMINAL.has(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); } -console.log(run.result.output); +const run = await client.runs.get(created.id); +console.log(run.result); ``` -``` -[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" -``` +The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. -## 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. +## Cancel a run ```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 +cancelled = await client.runs.cancel(created.id) +print(cancelled.status) ``` ```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 +const cancelled = await client.runs.cancel(created.id); +console.log(cancelled.status); ``` - - `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)); -} -``` - +Cancelling a run does not delete its session. You can send another turn with the same session ID. ## 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 +- [Get run events](/cloud/api-v4/runs/get-run-events) — event response and cursor fields +- [Get run status](/cloud/api-v4/runs/get-run-status) — lightweight poll target +- [Follow-up tasks](/cloud/agent/follow-up-tasks) — continue or queue work in the same session diff --git a/docs/cloud/agent/structured-output.mdx b/docs/cloud/agent/structured-output.mdx index 9a0abe0b..ca69dcca 100644 --- a/docs/cloud/agent/structured-output.mdx +++ b/docs/cloud/agent/structured-output.mdx @@ -1,18 +1,18 @@ --- title: Structured output -description: "Get validated, typed data back from agent tasks." +description: "Ask for JSON, then validate the V4 run 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. +V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. + V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel class Post(BaseModel): @@ -24,34 +24,46 @@ class HNPosts(BaseModel): posts: list[Post] client = AsyncBrowserUse() -result = await client.run( - "List the top 20 posts on Hacker News today with their points", - output_schema=HNPosts, +created = await client.runs.create( + """ + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + """ ) -for post in result.output.posts: - print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = await client.runs.wait_for_completion(created.id) +posts = HNPosts.model_validate_json(run.result or "{}") + +for post in posts.posts: + print(f"{post.name} ({post.points} pts)") ``` ```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(), - points: z.number(), - comments: z.number(), -}); - const HNPosts = z.object({ - posts: z.array(Post), + posts: z.array(z.object({ + name: z.string(), + points: z.number(), + comments: z.number(), + })), }); 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 created = await client.runs.create({ + task: ` + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + `, +}); +const run = await client.runs.waitForCompletion(created.id); +const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); + +for (const post of posts.posts) { + console.log(`${post.name} (${post.points} pts)`); } ``` + +For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. diff --git a/docs/cloud/agent/workspaces.mdx b/docs/cloud/agent/workspaces.mdx index f4f51eee..ded3c5af 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -1,198 +1,116 @@ --- title: Workspaces & files -description: "Upload files for the agent, download files the agent creates." +description: "Give a V4 run input files and retrieve files the agent creates." icon: folder --- -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it +## Upload and attach input files -## Upload a file +Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +workspace = await client.workspaces.create(name="company-research") +uploaded = await 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", +created = await client.runs.create( + "Read the attached people.csv and tell me who works at Google", workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], ) -print(result.output) +run = await client.runs.wait_for_completion(created.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 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 }, -); -console.log(result.output); +const workspace = await client.workspaces.create({ name: "company-research" }); +const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); + +const created = await client.runs.create({ + task: "Read the attached people.csv and tell me who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` -You can upload multiple files at once: +You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. ```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"); -``` - - -## Download files - - -```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, +uploaded = await client.workspaces.upload( + workspace.id, + "data.csv", + "config.json", + "image.png", ) - -# 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}") ``` ```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 uploaded = await client.workspaces.upload( + workspace.id, + "data.csv", + "config.json", + "image.png", ); - -// 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); -} -await client.workspaces.delete(workspaceId); -``` - + + Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. + -## Organize with prefixes +## Retrieve files the agent creates -Use `prefix` to organize files into directories within a workspace: +Ask the agent to save its output in the workspace, then list files with temporary download URLs: ```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) +created = await client.runs.create( + "Save the top three Hacker News posts as outputs/posts.json", + workspace_id=workspace.id, +) +await client.runs.wait_for_completion(created.id) -# Download only files from a subdirectory -await client.workspaces.download_all(workspace.id, to="./output", prefix="reports/") +files = await client.workspaces.files( + workspace.id, + prefix="outputs/", + include_urls=True, +) +for file in files.files: + print(file.path, file.url) ``` ```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); +const created = await client.runs.create({ + task: "Save the top three Hacker News posts as outputs/posts.json", + workspaceId: workspace.id, +}); +await client.runs.waitForCompletion(created.id); + +const files = await client.workspaces.files(workspace.id, { + prefix: "outputs/", + includeUrls: true, +}); +for (const file of files.files) { + console.log(file.path, file.url); } - -// 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`); -``` - +Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. -## Cloud dashboard +## Reuse a workspace -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). +- Pass neither ID to `runs.create()` to create a new session and workspace. +- Pass `session_id` / `sessionId` to continue the same conversation and workspace. +- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. - - Deleting a workspace permanently removes all its files. This cannot be undone. - +See [Upload workspace files](/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. diff --git a/docs/cloud/api-v4-overview.mdx b/docs/cloud/api-v4-overview.mdx index 0e232acf..bf9e7888 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/faq.mdx b/docs/cloud/faq.mdx index 878e9bd3..945231d5 100644 --- a/docs/cloud/faq.mdx +++ b/docs/cloud/faq.mdx @@ -6,19 +6,27 @@ 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) +created = await client.runs.create("Go to example.com") +await client.runs.wait_for_completion(created.id) +events = await 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 +40,22 @@ 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 vs v3 vs 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 new agent integrations.** It is designed for long-horizon work: -- **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 queued and interrupting 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. +V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. ```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse +# v4 (recommended for new agent runs) +from browser_use_sdk.v4 import AsyncBrowserUse -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse +# v3 (existing session-based integrations) +from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 ``` diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 8da85e61..4e59cff7 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -24,21 +24,25 @@ export BROWSER_USE_API_KEY=your_key ```python Python import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse async def main(): -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) + client = AsyncBrowserUse() + created = await client.runs.create("List the top 20 Hacker News posts and their points") + run = await client.runs.wait_for_completion(created.id) + print(run.result) asyncio.run(main()) ``` ```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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` Want a full working app? Check out the [Chat UI example](https://docs.browser-use.com/cloud/tutorials/chat-ui). @@ -47,17 +51,17 @@ Want a full working app? Check out the [Chat UI example](https://docs.browser-us | | **Agent** | **Browser** | |---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | +| **Method** | `runs.create()` | `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 | — | ✓ | +| proxy | `browserSettings` | ✓ | +| custom proxy | `browserSettings` | ✓ | +| profile | `browserSettings` | ✓ | +| recording | `browserSettings` | ✓ | +| workspace & files | ✓ | — | +| follow-up conversation | ✓ | — | +| screen size | `browserSettings` | ✓ | | timeout | — | ✓ | --- @@ -76,934 +80,562 @@ https://docs.browser-use.com/cloud/llms.txt ``` -# Agent Sign Up for Browser Use -Source: https://docs.browser-use.com/cloud/agent-signup - - -An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. - -The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. - -## REST flow - -### 1. Request a challenge - -```bash -curl -X POST https://api.browser-use.com/cloud/signup \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -Request body, optional (include a user email/name if available): - -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: - -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -### 2. Solve the challenge - -Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. - -### 3. Verify the answer - -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` - -Request body: - -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: - -```json -{ - "api_key": "bu_..." -} -``` - -Use the returned key for Browser Use Cloud API requests. - -For example, create a browser session: - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). - -## Claim the account - -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: - -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` - -Response: - -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. - # Introduction 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`. - -`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. +The SDK wraps the [API v4 Reference](https://docs.browser-use.com/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +created = await client.runs.create("List the top 20 Hacker News posts and their points") +run = await client.runs.wait_for_completion(created.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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task": "List the top 20 Hacker News posts and their points"}' ``` -**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 +`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status), then fetches the full [run result](https://docs.browser-use.com/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +Use the agent for: -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. +- Data extraction and research across many pages +- Form filling, downloads, and multi-step workflows +- Authenticated work with browser profiles +- Long-running tasks that create or consume files +- Follow-up turns that preserve the same conversation, workspace, and live browser + +See [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks), [Live messages](https://docs.browser-use.com/cloud/agent/streaming), and [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces) for the main V4 patterns. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` to select a model: +Pass `model` when you create a run. These are the models currently shown in the V4 agent UI: + +| Model | API string | Input | Cache read | Output | Bring your own key | +| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | +Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", +created = await client.runs.create( + "Compare the top three project-management tools for a 20-person startup", + model="claude-opus-5", ) -print(result.output) +run = await client.runs.wait_for_completion(created.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", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const created = await client.runs.create({ + task: "Compare the top three project-management tools for a 20-person startup", + model: "claude-opus-5", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -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"}' + -d '{"task": "Compare the top three project-management tools", "model": "claude-opus-5"}' ``` +## Bring your own key + +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. + +With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. + +Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. + # 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. +V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. + V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel class Post(BaseModel): -name: str -points: int -comments: int + name: str + points: int + comments: int class HNPosts(BaseModel): -posts: list[Post] + posts: list[Post] client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, +created = await client.runs.create( + """ + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + """ ) -for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = await client.runs.wait_for_completion(created.id) +posts = HNPosts.model_validate_json(run.result or "{}") + +for post in posts.posts: + print(f"{post.name} ({post.points} pts)") ``` ```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(), - points: z.number(), - comments: z.number(), -}); - const HNPosts = z.object({ - posts: z.array(Post), + posts: z.array(z.object({ + name: z.string(), + points: z.number(), + comments: z.number(), + })), }); 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 created = await client.runs.create({ + task: ` + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + `, +}); +const run = await client.runs.waitForCompletion(created.id); +const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); + +for (const post of posts.posts) { + console.log(`${post.name} (${post.points} pts)`); } ``` +For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. + # Follow-up tasks Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks -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. +Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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, +first = await client.runs.create( + "Go to amazon.com, search for laptops, and open the first result" ) +first_result = await client.runs.wait_for_completion(first.id) -await client.sessions.stop(session.id) +follow_up = await client.runs.create( + "Extract the customer reviews", + session_id=first.session_id, +) +follow_up_result = await client.runs.wait_for_completion(follow_up.id) +print(follow_up_result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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: "Go to amazon.com, search for laptops, and open the first result", }); +await client.runs.waitForCompletion(first.id); -await client.sessions.stop(session.id); +const followUp = await client.runs.create({ + task: "Extract the customer reviews", + 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. +The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. +There is no separate empty-session creation step in V4: -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming +- Omit `session_id` / `sessionId` to create a new session implicitly. +- Pass a previous session ID to continue it explicitly. +- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. +## Queue a follow-up - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/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](https://docs.browser-use.com/cloud/api-v3/sessions/list-session-messages) for all fields. +Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. ```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) +queued = await client.sessions.send_message( + first.session_id, + "Also compare the warranty options", +) ``` ```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); +const queued = await client.sessions.sendMessage(first.sessionId, { + text: "Also compare the warranty options", +}); ``` -``` -[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" -``` +Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](https://docs.browser-use.com/cloud/api-v4/sessions/get-session) or [List runs](https://docs.browser-use.com/cloud/api-v4/runs/list-runs) to discover the new run once it starts. -## Cancel a running task +See [Queue session message](https://docs.browser-use.com/cloud/api-v4/sessions/queue-session-message) for the full request shape. -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 -``` +# Live messages +Source: https://docs.browser-use.com/cloud/agent/streaming - `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 +V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. -If you need full control over the polling loop (e.g. custom interval, filtering): +Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. ```python Python import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse + +TERMINAL = {"completed", "failed", "cancelled"} client = AsyncBrowserUse() -session = await client.sessions.create(task="Find the top story on Hacker News") +created = await client.runs.create("Find the top story on Hacker News") -cursor = None +after = 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 + page = await client.runs.events(created.id, after=after, limit=100) + for event in page.events: + print(event.type, event.data) + if page.next_after is not None: + after = page.next_after -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) + status = await client.runs.status(created.id) + if status.status.value in TERMINAL: + break + await asyncio.sleep(1) -print(s.output) +run = await client.runs.get(created.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; +const TERMINAL = new Set(["completed", "failed", "cancelled"]); const client = new BrowserUse(); -const session = await client.sessions.create({ +const created = await client.runs.create({ task: "Find the top story on Hacker News", }); -let cursor: string | undefined; +let after: number | 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 page = await client.runs.events(created.id, { after, limit: 100 }); + for (const event of page.events) { + console.log(event.type, event.data); } + if (page.nextAfter != null) after = page.nextAfter; - 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)); + const { status } = await client.runs.status(created.id); + if (TERMINAL.has(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); } + +const run = await client.runs.get(created.id); +console.log(run.result); +``` + +The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. + +## Cancel a run + +```python Python +cancelled = await client.runs.cancel(created.id) +print(cancelled.status) +``` +```typescript TypeScript +const cancelled = await client.runs.cancel(created.id); +console.log(cancelled.status); ``` +Cancelling a run does not delete its session. You can send another turn with the same session ID. + ## 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 +- [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) — event response and cursor fields +- [Get run status](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status) — lightweight poll target +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue or queue work in the same session # Workspaces & files Source: https://docs.browser-use.com/cloud/agent/workspaces -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it +## Upload and attach input files -## Upload a file +Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +workspace = await client.workspaces.create(name="company-research") +uploaded = await 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", -workspace_id=workspace.id, +created = await client.runs.create( + "Read the attached people.csv and tell me who works at Google", + workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], ) -print(result.output) +run = await client.runs.wait_for_completion(created.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 workspace = await client.workspaces.create({ name: "my-workspace" }); +const workspace = await client.workspaces.create({ name: "company-research" }); +const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); -// 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 }, -); -console.log(result.output); +const created = await client.runs.create({ + task: "Read the attached people.csv and tell me who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` -You can upload multiple files at once: +You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. ```python Python -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png") +uploaded = 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"); +const uploaded = await client.workspaces.upload( + workspace.id, + "data.csv", + "config.json", + "image.png", +); ``` -## Download files + Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Retrieve files the agent creates -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +Ask the agent to save its output in the workspace, then list files with temporary download URLs: -# 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, +```python Python +created = await client.runs.create( + "Save the top three Hacker News posts as outputs/posts.json", + workspace_id=workspace.id, ) +await client.runs.wait_for_completion(created.id) -# 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}") +files = await client.workspaces.files( + workspace.id, + prefix="outputs/", + include_urls=True, +) +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 }, -); - -// Download a single file -await client.workspaces.download(workspace.id, "posts.json", { to: "./posts.json" }); +const created = await client.runs.create({ + task: "Save the top three Hacker News posts as outputs/posts.json", + workspaceId: workspace.id, +}); +await client.runs.waitForCompletion(created.id); -// Or download everything -const paths = await client.workspaces.downloadAll(workspace.id, { to: "./output" }); -for (const p of paths) { - console.log(`Downloaded: ${p}`); +const files = await client.workspaces.files(workspace.id, { + prefix: "outputs/", + includeUrls: true, +}); +for (const file of files.files) { + console.log(file.path, file.url); } ``` -## 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); -} -await client.workspaces.delete(workspaceId); -``` +Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. -## Organize with prefixes +## Reuse a workspace -Use `prefix` to organize files into directories within a workspace: +- Pass neither ID to `runs.create()` to create a new session and workspace. +- Pass `session_id` / `sessionId` to continue the same conversation and workspace. +- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. -```python Python -# Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") +See [Upload workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. -# 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/" }); +# Deterministic rerun +Source: https://docs.browser-use.com/cloud/agent/cache-script -// 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/" }); -``` +For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. -## List and delete files +You can create the workspace in the dashboard or through the API: ```python Python -# List all files -files = await client.workspaces.files(workspace.id) -for f in files.files: -print(f.path, f.size) +from browser_use_sdk.v4 import AsyncBrowserUse -# 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") +client = AsyncBrowserUse() +workspace = await client.workspaces.create(name="hn-scraper") + +created = await client.runs.create( + """ + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + """, + workspace_id=workspace.id, +) +first = await client.runs.wait_for_completion(created.id) +print(first.result) ``` ```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); -} +import { BrowserUse } from "browser-use-sdk/v4"; -// 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`); +const client = new BrowserUse(); +const workspace = await client.workspaces.create({ name: "hn-scraper" }); + +const created = await client.runs.create({ + task: ` + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + `, + workspaceId: workspace.id, +}); +const first = await client.runs.waitForCompletion(created.id); +console.log(first.result); ``` -## Cloud dashboard +Later, start a new run in the same workspace and tell the agent to use the saved script: -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). +```python Python +created = await client.runs.create( + "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspace_id=workspace.id, +) +rerun = await client.runs.wait_for_completion(created.id) +print(rerun.result) +``` +```typescript TypeScript +const created = await client.runs.create({ + task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspaceId: workspace.id, +}); +const rerun = await client.runs.waitForCompletion(created.id); +console.log(rerun.result); +``` - Deleting a workspace permanently removes all its files. This cannot be undone. +This pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. + V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script +# Human in the loop +Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -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 a human checkpoint for approvals, payments, complex authentication, or reviewing work before the agent continues. -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. +The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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), +created = await client.runs.create( + "Find noise-cancelling headphones on Amazon and stop before selecting a product" ) +await client.runs.wait_for_completion(created.id) + +events = await client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +live_url = ready.data["live_view_url"] +print(f"Open this live browser: {live_url}") + +input("Press Enter after selecting a product...") -# 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), +follow_up = await client.runs.create( + "Get the selected product's name, price, and rating", + session_id=created.session_id, ) +result = await client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```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. - - -# Human in the loop -Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop - - -## 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 - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# 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, -) -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...") - -# 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) - -# Clean up -await client.sessions.stop(session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; +import { BrowserUse } from "browser-use-sdk/v4"; +import * as readline from "node:readline/promises"; const client = new BrowserUse(); +const created = await client.runs.create({ + task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +}); +await client.runs.waitForCompletion(created.id); -// 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); +const events = await client.runs.events(created.id, { limit: 100 }); +const ready = events.events.find((event) => event.type === "browser.ready"); +const liveUrl = ready?.data.live_view_url; +console.log(`Open this live browser: ${liveUrl}`); -// 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), -); +await rl.question("Press Enter after selecting a product..."); 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); +const followUp = await client.runs.create({ + task: "Get the selected product's name, price, and rating", + sessionId: created.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); ``` +The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. + + Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. + +See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) for the event response. # Introduction Stealth @@ -1080,12 +712,12 @@ 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", -}, + custom_proxy={ + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + }, ) ``` ```typescript TypeScript @@ -1094,10 +726,10 @@ 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", + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", }, }); ``` @@ -1194,14 +826,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Check how many GitHub stars browser-use has", -enable_recording=True, + "Check how many GitHub stars browser-use has", + enable_recording=True, ) # Waits up to 15s for recording to be ready. Returns [] if no browser was opened. urls = await client.sessions.wait_for_recording(result.id) for url in urls: -print(url) # presigned MP4 download URL + print(url) # presigned MP4 download URL ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1260,11 +892,11 @@ from playwright.async_api import async_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" async with async_playwright() as p: -browser = await p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await browser.close() + browser = await p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -1304,11 +936,11 @@ from playwright.sync_api import sync_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" with sync_playwright() as p: -browser = p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -page.goto("https://example.com") -print(page.title()) -browser.close() + browser = 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. @@ -1340,11 +972,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com print(browser.live_url) # https://live.browser-use.com?wss=... async with async_playwright() as p: -pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) -page = pw_browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await pw_browser.close() + pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw_browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await pw_browser.close() await client.browsers.stop(browser.id) ``` @@ -1437,7 +1069,7 @@ profile = await client.profiles.create(name="work-account") # List all response = await client.profiles.list() for p in response.items: -print(p.id, p.name) + print(p.id, p.name) # Search by name response = await client.profiles.list(query="user-id-1") @@ -1603,8 +1235,8 @@ 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, + "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 @@ -1612,8 +1244,8 @@ 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, + "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) @@ -1662,14 +1294,14 @@ 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 + """ + 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) ``` @@ -1719,16 +1351,16 @@ client = AsyncBrowserUse() 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: + 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() + import pyotp + totp = pyotp.TOTP("{totp_secret}") + code = totp.now() -Enter the generated code. -""", + Enter the generated code. + """, ) print(result.output) ``` @@ -1772,6 +1404,187 @@ Use **Agent Mail** (enabled by default). For end-client scenarios, have them for Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code + + +[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 +``` + +**2. Verify the installation** + +```bash +browser-use doctor +``` + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install +``` + +**4. Authenticate for cloud browsers** + +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login +``` + +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. + +**5. Use it** + +Claude Code uses its bash tool to run CLI commands directly: + +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` + +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). + +## Agent Self-Registration + +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 +uv tool install browser-use +browser-use skill install +``` + +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: + +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +### 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. + +## 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](https://docs.browser-use.com/cloud/guides/x402). + + +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents + + +[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. + +The sandbox can't run a local browser, so the agent starts a named Browser Use Cloud browser and drives it with `browser-use <<'PY'` Python snippets. + +## 1. Create an environment + +Pre-install the CLI so it's ready at session start (no runtime install). + +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true +``` + +## 2. Create a credential vault + +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). + +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | + +## 3. Create the agent + +Tell it to use the CLI in cloud mode. + +```yaml +name: browser agent +model: + id: claude-opus-4-8 +description: Drives a stealth cloud browser with the Browser Use CLI. +system: | + You are a browser agent. Use the `browser-use` CLI to complete web tasks. + Never launch a local browser in this sandbox. Start a named cloud browser: + browser-use <<'PY' + start_remote_daemon("managed") + PY + Then run browser work through the same name: + BU_NAME=managed browser-use <<'PY' + new_tab("https://example.com") + print(page_info()) + PY + Your BROWSER_USE_API_KEY is in the environment; never print it. +tools: + - type: agent_toolset_20260401 # shell access so the agent can run the CLI + default_config: + enabled: true + permission_policy: + type: always_allow +``` + +## 4. Start a session and send a task + +The Console only observes; kick the agent off with a `user.message` event. + +```bash +curl -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: managed-agents-2026-04-01" \ + -H "content-type: application/json" \ + -d '{"events":[{"type":"user.message","content":[{"type":"text", + "text":"Get the top 5 Hacker News stories with their links."}]}]}' +``` + +## 5. Watch it run + +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 @@ -1799,16 +1612,16 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: ```json5 { browser: { -enabled: true, -defaultProfile: "browser-use", -remoteCdpTimeoutMs: 3000, -remoteCdpHandshakeTimeoutMs: 5000, -profiles: { - "browser-use": { - cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", - color: "#ff750e", - }, -}, + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, }, } ``` @@ -1846,7 +1659,7 @@ The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser a **1. Install the CLI** ```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash +uv tool install browser-use ``` **2. Verify the installation** @@ -1863,40 +1676,172 @@ Paste this setup prompt into your OpenClaw agent: Install or upgrade browser-use with `uv tool install --python 3.12 --upgrade --force 'browser-use @ git+https://github.com/browser-use/browser-use.git'`, run `browser-use skill install`, and connect it to my browser. Follow https://github.com/browser-use/browser-use if setup or connection fails. ``` -Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to navigate pages, click elements, fill forms, take screenshots, extract data, and more. The skill file teaches the agent the full command set. +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. -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). +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -``` -https://api.browser-use.com/v3/mcp -``` +[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. -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). +Two ways to set it up: configure Browser Use as Hermes's cloud browser backend, or install the Browser Use CLI and let Hermes drive it directly. -## Claude Code +## 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. + +### Setup + +**1. Get your API key** + +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). + +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. + +**2. Configure Hermes** + +Run the setup wizard: ```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp +hermes setup tools ``` -## Claude Desktop +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -Add to `claude_desktop_config.json`: +Or configure manually — add your key to `~/.hermes/.env`: + +```bash +BROWSER_USE_API_KEY=your_key_here +``` + +And set the provider in `~/.hermes/config.yaml`: + +```yaml +browser: + cloud_provider: browser-use +``` + +**3. Use it** + +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: + +``` +> Find the top trending repositories on GitHub today and summarize them +``` + +## Option 2: Browser Use CLI + +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. + +### Setup + +**1. Install the CLI** + +```bash +uv tool install browser-use +``` + +**2. Verify the installation** + +```bash +browser-use doctor +``` + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install +``` + +Or ask Hermes directly in chat to install it. + +**4. Authenticate for cloud browsers** + +Authenticate with your API key: + +```bash +browser-use auth login +``` + +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. + +**5. Use it** + +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: + +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` + +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). + +## Agent Self-Registration + +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. + +Install the Browser Use CLI and skill: + +```bash +uv tool install browser-use +browser-use skill install +``` + +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** + +For the cloud browser backend (Option 1): + +```bash +hermes config set BROWSER_USE_API_KEY +``` + +For CLI mode (Option 2), put the key in the agent's shell environment: + +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +### 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. + + +# 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" - } -} + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1908,12 +1853,12 @@ 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" - } -} + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1925,12 +1870,12 @@ 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" - } -} + "browser-use": { + "serverUrl": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1968,10 +1913,10 @@ Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.b "type": "agent.task.status_update", "timestamp": "2025-01-15T10:30:00Z", "payload": { -"task_id": "task_abc123", -"session_id": "session_xyz", -"status": "idle", -"metadata": {} + "task_id": "task_abc123", + "session_id": "session_xyz", + "status": "idle", + "metadata": {} } } ``` @@ -1992,17 +1937,17 @@ 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) + # 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"; @@ -2010,12 +1955,12 @@ 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 Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); } return obj; } @@ -2030,102 +1975,420 @@ function verifyWebhook(body: string, signature: string, timestamp: string, secre } ``` -## Example: Express webhook handler +## Example: Express webhook handler + +```typescript +import express from "express"; +import { createHmac, timingSafeEqual } from "crypto"; + +const app = express(); +app.use(express.raw({ type: "application/json" })); + +const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +app.post("/webhook", (req, res) => { + const signature = req.headers["x-browser-use-signature"] as string; + const timestamp = req.headers["x-browser-use-timestamp"] as string; + + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { + return res.status(401).send("Request too old"); + } + + const body = req.body.toString(); + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); + + if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { + return res.status(401).send("Invalid signature"); + } + + if (payload.type === "agent.task.status_update") { + const { task_id, status, session_id } = payload.payload; + console.log(`Task ${task_id} is now ${status}`); + } + + res.status(200).send("OK"); +}); + +app.listen(3000); +``` + +## Example: FastAPI webhook handler + +```python +from fastapi import FastAPI, Request, HTTPException +import hashlib +import hmac +import json +import os +import time + +app = FastAPI() + +WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] + +@app.post("/webhook") +async def handle_webhook(request: Request): + body = await request.body() + signature = request.headers.get("x-browser-use-signature", "") + timestamp = request.headers.get("x-browser-use-timestamp", "") + + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + raise HTTPException(status_code=401, detail="Invalid timestamp") + if abs(time.time() - ts) > 300: + raise HTTPException(status_code=401, detail="Request too old") + + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() + + if not hmac.compare_digest(expected, signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + if payload["type"] == "agent.task.status_update": + task_id = payload["payload"]["task_id"] + status = payload["payload"]["status"] + print(f"Task {task_id} is now {status}") + + return {"status": "ok"} +``` + + For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. + + +# x402 (pay-per-request) +Source: https://docs.browser-use.com/cloud/guides/x402 + + + + +[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. + +x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. + + +**New to crypto?** Here's the gist: + +- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. +- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. +- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. +- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. + + +**Three ways to start, ranked by laziness:** + +One command. Claude does the wallet setup, funding walkthrough, and +verification for you. +One line in your Python or TypeScript app. Bring your own wallet. +Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. + +## Claude Code quickstart + +The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: + +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` + +Then in Claude Code: + +``` +> /x402 +``` + +Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. + + Already have a Browser Use Cloud account? The skill detects this and switches + to **top-up mode**, adding credits to that existing account instead of + creating a new, wallet-keyed one. + +## SDK quickstart + +The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. + +```bash Python +pip install "browser-use-sdk[x402]" +``` +```bash TypeScript +npm install browser-use-sdk @x402/fetch @x402/evm viem +``` + +```python Python +import asyncio +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base + result = await client.run("Go to example.com and tell me the heading.") + print(result.output) + +asyncio.run(main()) +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base +const result = await client.run("Go to example.com and tell me the heading."); +console.log(result.output); +``` + +Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: + +```python Python +client = AsyncBrowserUse() # auto-detects from env +``` +```typescript TypeScript +const client = new BrowserUse(); // auto-detects from env +``` + + Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. + +## Raw HTTP quickstart + +Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): + +```python +import asyncio + +from x402 import x402Client +from x402.http.clients import x402HttpxClient +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account + +async def main(): + client = x402Client() + register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) + + async with x402HttpxClient(client, timeout=120.0) as http: + response = await http.post( + "https://x402.api.browser-use.com/api/v3/sessions", + json={"task": "..."}, + ) + print(response.status_code, response.text[:500]) + +asyncio.run(main()) +``` + +`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. + +## What you need + +- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app +- **USD Coin (USDC) on Base mainnet** +- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) + +You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. + + +## Pricing and credits + +Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** + + **Mid-task drain still terminates the task.** Browser Use sessions run on a + worker that doesn't see x402, so once a long-running task starts and burns + through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause + and wait for the next x402 payment. The `$5` default exists so most tasks + complete without hitting this; for expensive models (e.g. Opus) or long + sessions, pre-fund with multiple requests before kicking off the task. + +See the [pricing page](https://browser-use.com/pricing) for model and browser costs. + +## Topping up an existing account + +If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: + +```python Python +import asyncio + +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse( + api_key="bu_...", # existing API key getting topped up + x402_private_key="0x...", # wallet that pays + base_url="https://x402.api.browser-use.com/api/v3", +) +async def main(): + result = await client.run("...") # $5 USDC charged, credited to the API key's project + print(result.output) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ + apiKey: "bu_...", + x402PrivateKey: "0x...", + baseUrl: "https://x402.api.browser-use.com/api/v3", +}); +const result = await client.run("..."); +``` + +When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: + +- Agents that ran out of free-tier credits and need to keep going +- Adding credits via crypto when you already have a regular Browser Use account +- Multi-wallet setups funding one shared account + +## Checking your credit balance + +When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. + +This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. + +To check how much credit that account has left, use the method below: + +```python Python +import asyncio + +from browser_use_sdk.v3 import get_wallet_balance + +async def main(): + balance = await get_wallet_balance("0x...") # same wallet private key you pay with + print(balance["total_credits_usd"]) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { getWalletBalance } from "browser-use-sdk/v3"; + +const balance = await getWalletBalance("0x..."); // same wallet private key you pay with +console.log(balance.total_credits_usd); +``` + +The response contains: + +| Field | Description | +| ------------------------ | ------------------------------------------------------------------------------- | +| `wallet` | The wallet address (lowercased) | +| `project_id` | The account (project) tied to your wallet that the credits live in | +| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | +| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | + + This is for accounts created from a wallet (the default x402 mode). If you're + [topping up an existing account](#topping-up-an-existing-account), check that + account's balance the normal way with your API key via + `client.billing.account()`. A wallet that has never paid yet has no account, + so the call returns `404` until the first payment. + + The SDK signs a fixed, server-defined message + ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with + Ethereum" mechanism) with your wallet's private key. The signature proves you + control the address without moving any funds. The server recovers the signer, + matches it to the wallet's project, and returns the balance. + +## How it works + +Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. + +A bit more detail: + +1. Your code makes a request (e.g. "run this task"). +2. The SDK auto-signs the payment from your wallet and resends the request. +3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. +4. We run your task and send back the result. -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; +## Wallet setup -const app = express(); -app.use(express.raw({ type: "application/json" })); +If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; +Get the [MetaMask browser extension](https://metamask.io) via the official +site only. Create a new wallet, save the seed phrase somewhere offline, set +a password. +By default, most wallets only show Ethereum. You need to add **Base** (the +network we accept payments on) so your wallet can hold USDC there. +Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and +pay with credit card, bank, etc. The USDC lands directly in your wallet. +In MetaMask: click the account menu → **Account details** → **Private keys** +→ enter your password → copy. That string (starts with `0x`) is your +`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in +their account settings. -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; -} + Wallets hold real money, and anyone with the private key can drain it. Be + careful with your keys. -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; +## Advanced: bring your own x402 client - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { -return res.status(401).send("Request too old"); - } +For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: - 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"); +```python Python +from x402 import x402Client +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account +from browser_use_sdk.v3 import AsyncBrowserUse - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { -return res.status(401).send("Invalid signature"); - } +x402 = x402Client() +register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) +client = AsyncBrowserUse(x402=x402) - 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"); -}); +```typescript TypeScript +import { x402Client } from "@x402/fetch"; +import { ExactEvmScheme } from "@x402/evm"; +import { privateKeyToAccount } from "viem/accounts"; +import { BrowserUse } from "browser-use-sdk/v3"; -app.listen(3000); +const x402 = new x402Client(); +x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); +const client = new BrowserUse({ x402 }); ``` -## Example: FastAPI webhook handler +## Troubleshooting -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time +Two likely causes: -app = FastAPI() +- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. +- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). -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", "") + You haven't installed the optional x402 deps. Run `pip install + "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` + (TypeScript). -# 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") + We verified your payment request but couldn't credit your project, so we + deliberately did not settle on-chain. No USDC was moved, so just retry. This + is rare. -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() + Wait a few seconds. Settlement and credit grant happen in the same request, + but the response may be sent before the credit grant fully commits. If credits + still show `$0` after a few minutes, contact support with your wallet address. + (Conversely, if a payment settles but the request itself then fails, we + automatically reclaim the credits so you aren't charged for nothing.) -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. -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}") +## Related -return {"status": "ok"} -``` +- [x402 protocol spec](https://www.x402.org) +- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use +- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) - 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. + # n8n @@ -2223,8 +2486,8 @@ import { client } from "./api"; export async function createSession() { const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, + keepAlive: true, + enableRecording: true, }); return { id: session.id, liveUrl: session.liveUrl, status: session.status }; } @@ -2241,7 +2504,7 @@ async function handleSend(message: string) { const session = await createSession(); router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` + `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` ); } ``` @@ -2257,7 +2520,7 @@ const streamTask = useCallback(async (task: string) => { const run = client.run(task, { sessionId }); for await (const msg of run) { -setMessages((prev) => [...prev, msg]); + setMessages((prev) => [...prev, msg]); } // Iterator done — task reached terminal state @@ -2301,7 +2564,7 @@ useEffect(() => { if (!isTerminal) return; client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); + if (urls.length) setRecordingUrls(urls); }); }, [isTerminal, sessionId]); ``` @@ -2329,23 +2592,23 @@ The session page consumes everything through a context provider: ```typescript session/[id]/page.tsx function SessionPage() { const { session, turns, isBusy, isTerminal, recordingUrls, sendMessage, stopTask } = -useSession(); + useSession(); return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
+
+ {/* Chat column */} +
+ + +
+ + {/* Live browser view — liveUrl available from session creation */} + +
); } ``` @@ -2362,6 +2625,116 @@ useSession(); | `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup + + +An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. + +The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. + +## REST flow + +### 1. Request a challenge + +```bash +curl -X POST https://api.browser-use.com/cloud/signup \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +Request body, optional (include a user email/name if available): + +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` + +Response: + +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} +``` + +### 2. Solve the challenge + +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. + +### 3. Verify the answer + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/verify \ + -H "Content-Type: application/json" \ + -d '{"challenge_id":"uuid","answer":"144.00"}' +``` + +Request body: + +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} +``` + +Response: + +```json +{ + "api_key": "bu_..." +} +``` + +Use the returned key for Browser Use Cloud API requests. + +For example, create a browser session: + +```bash +curl -X POST https://api.browser-use.com/api/v3/browsers \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + +## Claim the account + +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." +``` + +Response: + +```json +{ + "claim_url": "https://..." +} +``` + +The claim URL is valid for 1 hour. + +## CLI usage + +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: + +```bash +uv tool install browser-use +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +Replace `bu_...` with the key returned by the REST flow. + + # Grow Therapy provider search Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare @@ -2398,28 +2771,28 @@ const client = new BrowserUse(); ```python Python class Provider(BaseModel): -name: str -title: str -specialties: list[str] -insurance_plans: list[str] -rating: float | None = None -next_available: str | None = None + name: str + title: str + specialties: list[str] + insurance_plans: list[str] + rating: float | None = None + next_available: str | None = None class ProviderSearch(BaseModel): -providers: list[Provider] -total_found: int | None = None -location: str -specialty: str + providers: list[Provider] + total_found: int | None = None + location: str + specialty: str ``` ```typescript TypeScript const ProviderSearch = z.object({ providers: z.array(z.object({ -name: z.string(), -title: z.string(), -specialties: z.array(z.string()), -insurancePlans: z.array(z.string()), -rating: z.number().nullable(), -nextAvailable: z.string().nullable(), + name: z.string(), + title: z.string(), + specialties: z.array(z.string()), + insurancePlans: z.array(z.string()), + rating: z.number().nullable(), + nextAvailable: z.string().nullable(), })), totalFound: z.number().nullable(), location: z.string(), @@ -2440,19 +2813,19 @@ const workspace = await client.workspaces.create({ name: "grow-therapy-search" } ```python Python result = await client.run( -"Go to growtherapy.com and search for therapists in {{New York}} " -"who specialize in {{anxiety}} and accept insurance. " -"Return the first 5 provider profiles as JSON.", -workspace_id=str(workspace.id), -output_schema=ProviderSearch, + "Go to growtherapy.com and search for therapists in {{New York}} " + "who specialize in {{anxiety}} and accept insurance. " + "Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, ) for p in result.output.providers: -print(f"{p.name} ({p.title})") -print(f" Specialties: {', '.join(p.specialties)}") -print(f" Rating: {p.rating}") -print(f" Next available: {p.next_available}") -print() + print(f"{p.name} ({p.title})") + print(f" Specialties: {', '.join(p.specialties)}") + print(f" Rating: {p.rating}") + print(f" Next available: {p.next_available}") + print() ``` ```typescript TypeScript const result = await client.run( @@ -2479,16 +2852,16 @@ locations = ["Los Angeles", "Chicago", "Houston", "Miami"] specialties = ["depression", "trauma", "ADHD"] for location in locations: -for specialty in specialties: - result = await client.run( - f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " - f"who specialize in {{{{{specialty}}}}} and accept insurance. " - f"Return the first 5 provider profiles as JSON.", - workspace_id=str(workspace.id), - output_schema=ProviderSearch, - ) - count = len(result.output.providers) - print(f"{location} / {specialty}: {count} providers found") + for specialty in specialties: + result = await client.run( + f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " + f"who specialize in {{{{{specialty}}}}} and accept insurance. " + f"Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, + ) + count = len(result.output.providers) + print(f"{location} / {specialty}: {count} providers found") ``` ```typescript TypeScript const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; @@ -2496,13 +2869,13 @@ const specialties = ["depression", "trauma", "ADHD"]; for (const location of locations) { for (const specialty of specialties) { -const result = await client.run( - `Go to growtherapy.com and search for therapists in {{${location}}} ` + - `who specialize in {{${specialty}}} and accept insurance. ` + - `Return the first 5 provider profiles as JSON.`, - { workspaceId: workspace.id, schema: ProviderSearch }, -); -console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); + const result = await client.run( + `Go to growtherapy.com and search for therapists in {{${location}}} ` + + `who specialize in {{${specialty}}} and accept insurance. ` + + `Return the first 5 provider profiles as JSON.`, + { workspaceId: workspace.id, schema: ProviderSearch }, + ); + console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); } } ``` @@ -2532,19 +2905,27 @@ 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) +created = await client.runs.create("Go to example.com") +await client.runs.wait_for_completion(created.id) +events = await 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,23 +2939,24 @@ 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 vs v3 vs 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 new agent integrations.** It is designed for long-horizon work: -- **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 queued and interrupting 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. +V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. ```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse +# v4 (recommended for new agent runs) +from browser_use_sdk.v4 import AsyncBrowserUse -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse +# v3 (existing session-based integrations) +from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 ``` @@ -2587,7 +2969,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 +3002,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 +3043,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 +3066,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 ``` @@ -2767,8 +3148,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 +3170,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) ``` @@ -2862,9 +3243,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 +3256,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 +3267,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"], }, ); ``` @@ -2924,9 +3305,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 +3317,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,28 +3329,85 @@ For SSO/OAuth redirects, include all domains in the auth flow: ```python Python result = await client.run( -"Log into the company portal and download the Q4 report", -secrets={ - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowed_domains=["portal.example.com", "*.okta.com"], + "Log into the company portal and download the Q4 report", + secrets={ + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowed_domains=["portal.example.com", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into the company portal and download the Q4 report", { -secrets: { - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowedDomains: ["portal.example.com", "*.okta.com"], + secrets: { + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowedDomains: ["portal.example.com", "*.okta.com"], }, ); ``` +# API Reference +Source: https://docs.browser-use.com/cloud/api-v4-overview + + +## Authentication + +All requests require an API key in the `X-Browser-Use-API-Key` header: + +``` +X-Browser-Use-API-Key: bu_your_key_here +``` + +Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. + +## Base URL + +``` +https://api.browser-use.com/api/v4 +``` + +## The core loop + +Create a run, poll its status until terminal, then fetch the full result. `status` is a cheap indexed lookup — poll it, not the full run. + +```bash Create a run +curl -X POST https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"task": "Find the top 3 trending repos on GitHub today"}' +``` + +```bash Poll status until completed | failed | cancelled (replace RUN_ID) +curl https://api.browser-use.com/api/v4/runs/RUN_ID/status \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +```bash Fetch the full run once it's terminal +curl https://api.browser-use.com/api/v4/runs/RUN_ID \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +## Sessions and follow-ups + +A run belongs to a session (a conversation). Send a follow-up message to a session's queue — it runs as the next turn, or immediately with `interrupt: true`: + +```bash Queue a follow-up (replace SESSION_ID) +curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"text": "Now open the top result", "interrupt": false}' +``` + +## SDKs + +The [Cloud SDK 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 Reference Source: https://docs.browser-use.com/cloud/api-reference @@ -3046,66 +3484,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 8b1c30c1..05bab8ad 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -1,15 +1,14 @@ # Browser Use Cloud SDK -> 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 is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (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. +**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. 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` @@ -23,18 +22,17 @@ 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. - [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. +- [Introduction](https://docs.browser-use.com/cloud/agent/quickstart): Run a long-horizon browser agent with one task and a few lines of code. +- [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, then validate the V4 run result in your application. +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Continue the same V4 conversation, workspace, and browser. +- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Poll V4 run events incrementally to monitor progress or build a custom UI. +- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Give a V4 run input files and retrieve files the agent creates. +- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save and test a reusable script, then run it again from the same workspace. +- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the V4 live browser, let a person take over, then continue the same session. ## 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. @@ -52,12 +50,19 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## 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. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. - [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. - [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. +- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. - [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. +## 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. +- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. - [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) @@ -67,70 +72,11 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [1Password & 2FA](https://docs.browser-use.com/cloud/guides/1password): Auto-fill passwords and TOTP codes from 1Password during agent tasks. - [Secrets](https://docs.browser-use.com/cloud/guides/secrets): Pass domain-scoped credentials to the agent securely. +## API v4 +- [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. + ## API v3 - [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. - -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/cloud/openapi/v4.json b/docs/cloud/openapi/v4.json index 0a15025f..ec1f6424 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 b461a5ff..a272577c 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -26,21 +26,25 @@ export BROWSER_USE_API_KEY=your_key ```python Python import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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) + created = await client.runs.create("List the top 20 Hacker News posts and their points") + run = await client.runs.wait_for_completion(created.id) + print(run.result) asyncio.run(main()) ``` ```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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` @@ -50,17 +54,17 @@ Want a full working app? Check out the [Chat UI example](/cloud/tutorials/chat-u | | **Agent** | **Browser** | |---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | +| **Method** | `runs.create()` | `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 | — | ✓ | +| proxy | `browserSettings` | ✓ | +| custom proxy | `browserSettings` | ✓ | +| profile | `browserSettings` | ✓ | +| recording | `browserSettings` | ✓ | +| workspace & files | ✓ | — | +| follow-up conversation | ✓ | — | +| screen size | `browserSettings` | ✓ | | timeout | — | ✓ | --- diff --git a/docs/docs.json b/docs/docs.json index 7b7fd70c..cddda63b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1008,6 +1008,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 +1109,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 6d2b70a3..806db709 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -224,16 +224,15 @@ CLOUD_FULL="$SCRIPT_DIR/llms-full.txt" cat > "$CLOUD_INDEX" << 'HEADER' # Browser Use Cloud SDK -> 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 is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (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. +**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. 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` diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 8da85e61..4e59cff7 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -24,21 +24,25 @@ export BROWSER_USE_API_KEY=your_key ```python Python import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse async def main(): -client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) + client = AsyncBrowserUse() + created = await client.runs.create("List the top 20 Hacker News posts and their points") + run = await client.runs.wait_for_completion(created.id) + print(run.result) asyncio.run(main()) ``` ```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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` Want a full working app? Check out the [Chat UI example](https://docs.browser-use.com/cloud/tutorials/chat-ui). @@ -47,17 +51,17 @@ Want a full working app? Check out the [Chat UI example](https://docs.browser-us | | **Agent** | **Browser** | |---|---|---| -| **Method** | `sessions.create()` / `run()` | `browsers.create()` | +| **Method** | `runs.create()` | `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 | — | ✓ | +| proxy | `browserSettings` | ✓ | +| custom proxy | `browserSettings` | ✓ | +| profile | `browserSettings` | ✓ | +| recording | `browserSettings` | ✓ | +| workspace & files | ✓ | — | +| follow-up conversation | ✓ | — | +| screen size | `browserSettings` | ✓ | | timeout | — | ✓ | --- @@ -76,934 +80,562 @@ https://docs.browser-use.com/cloud/llms.txt ``` -# Agent Sign Up for Browser Use -Source: https://docs.browser-use.com/cloud/agent-signup - - -An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. - -The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. - -## REST flow - -### 1. Request a challenge - -```bash -curl -X POST https://api.browser-use.com/cloud/signup \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -Request body, optional (include a user email/name if available): - -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: - -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -### 2. Solve the challenge - -Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. - -### 3. Verify the answer - -```bash -curl -X POST https://api.browser-use.com/cloud/signup/verify \ - -H "Content-Type: application/json" \ - -d '{"challenge_id":"uuid","answer":"144.00"}' -``` - -Request body: - -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: - -```json -{ - "api_key": "bu_..." -} -``` - -Use the returned key for Browser Use Cloud API requests. - -For example, create a browser session: - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). - -## Claim the account - -If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: - -```bash -curl -X POST https://api.browser-use.com/cloud/signup/claim \ - -H "X-Browser-Use-API-Key: bu_..." -``` - -Response: - -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. - # Introduction 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`. - -`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. +The SDK wraps the [API v4 Reference](https://docs.browser-use.com/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run("List the top 20 posts on Hacker News today with their points") -print(result.output) +created = await client.runs.create("List the top 20 Hacker News posts and their points") +run = await client.runs.wait_for_completion(created.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 created = await client.runs.create({ + task: "List the top 20 Hacker News posts and their points", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -H "X-Browser-Use-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"task": "List the top 20 posts on Hacker News today with their points"}' + -d '{"task": "List the top 20 Hacker News posts and their points"}' ``` -**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 +`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status), then fetches the full [run result](https://docs.browser-use.com/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. -The best SOTA browser agent — see our [online Mind2Web benchmark](https://browser-use.com/posts/online-mind2web-benchmark). +Use the agent for: -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. +- Data extraction and research across many pages +- Form filling, downloads, and multi-step workflows +- Authenticated work with browser profiles +- Long-running tasks that create or consume files +- Follow-up turns that preserve the same conversation, workspace, and live browser + +See [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks), [Live messages](https://docs.browser-use.com/cloud/agent/streaming), and [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces) for the main V4 patterns. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` to select a model: +Pass `model` when you create a run. These are the models currently shown in the V4 agent UI: + +| Model | API string | Input | Cache read | Output | Bring your own key | +| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | +Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. - We recommend **Claude Sonnet 4.6** (`claude-sonnet-4.6`). It's the model we optimize for the most right now. + **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -model="claude-sonnet-4.6", +created = await client.runs.create( + "Compare the top three project-management tools for a 20-person startup", + model="claude-opus-5", ) -print(result.output) +run = await client.runs.wait_for_completion(created.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", - { model: "claude-sonnet-4.6" }, -); -console.log(result.output); +const created = await client.runs.create({ + task: "Compare the top three project-management tools for a 20-person startup", + model: "claude-opus-5", +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v3/sessions \ +curl -X POST https://api.browser-use.com/api/v4/runs \ -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"}' + -d '{"task": "Compare the top three project-management tools", "model": "claude-opus-5"}' ``` +## Bring your own key + +Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. + +With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. + +Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. + # 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. +V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - TypeScript requires **Zod v4** (`npm install zod@4`). Zod v3 is not compatible. + V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel class Post(BaseModel): -name: str -points: int -comments: int + name: str + points: int + comments: int class HNPosts(BaseModel): -posts: list[Post] + posts: list[Post] client = AsyncBrowserUse() -result = await client.run( -"List the top 20 posts on Hacker News today with their points", -output_schema=HNPosts, +created = await client.runs.create( + """ + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + """ ) -for post in result.output.posts: -print(f"{post.name} ({post.points} pts, {post.comments} comments)") +run = await client.runs.wait_for_completion(created.id) +posts = HNPosts.model_validate_json(run.result or "{}") + +for post in posts.posts: + print(f"{post.name} ({post.points} pts)") ``` ```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(), - points: z.number(), - comments: z.number(), -}); - const HNPosts = z.object({ - posts: z.array(Post), + posts: z.array(z.object({ + name: z.string(), + points: z.number(), + comments: z.number(), + })), }); 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 created = await client.runs.create({ + task: ` + List the top 20 Hacker News posts. + Return JSON only in this shape: + {"posts": [{"name": "string", "points": 0, "comments": 0}]} + `, +}); +const run = await client.runs.waitForCompletion(created.id); +const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); + +for (const post of posts.posts) { + console.log(`${post.name} (${post.points} pts)`); } ``` +For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. + # Follow-up tasks Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks -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. +Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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, +first = await client.runs.create( + "Go to amazon.com, search for laptops, and open the first result" ) +first_result = await client.runs.wait_for_completion(first.id) -await client.sessions.stop(session.id) +follow_up = await client.runs.create( + "Extract the customer reviews", + session_id=first.session_id, +) +follow_up_result = await client.runs.wait_for_completion(follow_up.id) +print(follow_up_result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; 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: "Go to amazon.com, search for laptops, and open the first result", }); +await client.runs.waitForCompletion(first.id); -await client.sessions.stop(session.id); +const followUp = await client.runs.create({ + task: "Extract the customer reviews", + 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. +The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. +There is no separate empty-session creation step in V4: -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming +- Omit `session_id` / `sessionId` to create a new session implicitly. +- Pass a previous session ID to continue it explicitly. +- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. +## Queue a follow-up - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/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](https://docs.browser-use.com/cloud/api-v3/sessions/list-session-messages) for all fields. +Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. ```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) +queued = await client.sessions.send_message( + first.session_id, + "Also compare the warranty options", +) ``` ```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); +const queued = await client.sessions.sendMessage(first.sessionId, { + text: "Also compare the warranty options", +}); ``` -``` -[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" -``` +Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](https://docs.browser-use.com/cloud/api-v4/sessions/get-session) or [List runs](https://docs.browser-use.com/cloud/api-v4/runs/list-runs) to discover the new run once it starts. -## Cancel a running task +See [Queue session message](https://docs.browser-use.com/cloud/api-v4/sessions/queue-session-message) for the full request shape. -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 -``` +# Live messages +Source: https://docs.browser-use.com/cloud/agent/streaming - `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 +V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. -If you need full control over the polling loop (e.g. custom interval, filtering): +Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. ```python Python import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse + +TERMINAL = {"completed", "failed", "cancelled"} client = AsyncBrowserUse() -session = await client.sessions.create(task="Find the top story on Hacker News") +created = await client.runs.create("Find the top story on Hacker News") -cursor = None +after = 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 + page = await client.runs.events(created.id, after=after, limit=100) + for event in page.events: + print(event.type, event.data) + if page.next_after is not None: + after = page.next_after -s = await client.sessions.get(session.id) -if s.status.value in ("idle", "stopped", "error", "timed_out"): - break -await asyncio.sleep(2) + status = await client.runs.status(created.id) + if status.status.value in TERMINAL: + break + await asyncio.sleep(1) -print(s.output) +run = await client.runs.get(created.id) +print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; +import { BrowserUse } from "browser-use-sdk/v4"; +const TERMINAL = new Set(["completed", "failed", "cancelled"]); const client = new BrowserUse(); -const session = await client.sessions.create({ +const created = await client.runs.create({ task: "Find the top story on Hacker News", }); -let cursor: string | undefined; +let after: number | 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 page = await client.runs.events(created.id, { after, limit: 100 }); + for (const event of page.events) { + console.log(event.type, event.data); } + if (page.nextAfter != null) after = page.nextAfter; - 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)); + const { status } = await client.runs.status(created.id); + if (TERMINAL.has(status)) break; + await new Promise((resolve) => setTimeout(resolve, 1000)); } + +const run = await client.runs.get(created.id); +console.log(run.result); +``` + +The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. + +## Cancel a run + +```python Python +cancelled = await client.runs.cancel(created.id) +print(cancelled.status) +``` +```typescript TypeScript +const cancelled = await client.runs.cancel(created.id); +console.log(cancelled.status); ``` +Cancelling a run does not delete its session. You can send another turn with the same session ID. + ## 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 +- [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) — event response and cursor fields +- [Get run status](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status) — lightweight poll target +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue or queue work in the same session # Workspaces & files Source: https://docs.browser-use.com/cloud/agent/workspaces -Workspaces give your agent persistent file storage. Two patterns cover almost every use case: +Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. -1. **You upload a file** → agent reads it -2. **Agent creates a file** → you download it +## Upload and attach input files -## Upload a file +Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 import AsyncBrowserUse client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +workspace = await client.workspaces.create(name="company-research") +uploaded = await 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", -workspace_id=workspace.id, +created = await client.runs.create( + "Read the attached people.csv and tell me who works at Google", + workspace_id=workspace.id, + attached_file_ids=[uploaded[0].id], ) -print(result.output) +run = await client.runs.wait_for_completion(created.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 workspace = await client.workspaces.create({ name: "my-workspace" }); +const workspace = await client.workspaces.create({ name: "company-research" }); +const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); -// 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 }, -); -console.log(result.output); +const created = await client.runs.create({ + task: "Read the attached people.csv and tell me who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); +const run = await client.runs.waitForCompletion(created.id); +console.log(run.result); ``` -You can upload multiple files at once: +You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. ```python Python -await client.workspaces.upload(workspace.id, "data.csv", "config.json", "image.png") +uploaded = 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"); +const uploaded = await client.workspaces.upload( + workspace.id, + "data.csv", + "config.json", + "image.png", +); ``` -## Download files + Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +## Retrieve files the agent creates -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="my-workspace") +Ask the agent to save its output in the workspace, then list files with temporary download URLs: -# 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, +```python Python +created = await client.runs.create( + "Save the top three Hacker News posts as outputs/posts.json", + workspace_id=workspace.id, ) +await client.runs.wait_for_completion(created.id) -# 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}") +files = await client.workspaces.files( + workspace.id, + prefix="outputs/", + include_urls=True, +) +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 }, -); - -// Download a single file -await client.workspaces.download(workspace.id, "posts.json", { to: "./posts.json" }); +const created = await client.runs.create({ + task: "Save the top three Hacker News posts as outputs/posts.json", + workspaceId: workspace.id, +}); +await client.runs.waitForCompletion(created.id); -// Or download everything -const paths = await client.workspaces.downloadAll(workspace.id, { to: "./output" }); -for (const p of paths) { - console.log(`Downloaded: ${p}`); +const files = await client.workspaces.files(workspace.id, { + prefix: "outputs/", + includeUrls: true, +}); +for (const file of files.files) { + console.log(file.path, file.url); } ``` -## 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); -} -await client.workspaces.delete(workspaceId); -``` +Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. -## Organize with prefixes +## Reuse a workspace -Use `prefix` to organize files into directories within a workspace: +- Pass neither ID to `runs.create()` to create a new session and workspace. +- Pass `session_id` / `sessionId` to continue the same conversation and workspace. +- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. -```python Python -# Upload into a subdirectory -await client.workspaces.upload(workspace.id, "report.pdf", prefix="reports/") +See [Upload workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. -# 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/" }); +# Deterministic rerun +Source: https://docs.browser-use.com/cloud/agent/cache-script -// 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/" }); -``` +For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. -## List and delete files +You can create the workspace in the dashboard or through the API: ```python Python -# List all files -files = await client.workspaces.files(workspace.id) -for f in files.files: -print(f.path, f.size) +from browser_use_sdk.v4 import AsyncBrowserUse -# 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") +client = AsyncBrowserUse() +workspace = await client.workspaces.create(name="hn-scraper") + +created = await client.runs.create( + """ + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + """, + workspace_id=workspace.id, +) +first = await client.runs.wait_for_completion(created.id) +print(first.result) ``` ```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); -} +import { BrowserUse } from "browser-use-sdk/v4"; -// 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`); +const client = new BrowserUse(); +const workspace = await client.workspaces.create({ name: "hn-scraper" }); + +const created = await client.runs.create({ + task: ` + Get the top five Hacker News stories as JSON. + Then create helper functions or a script that performs exactly what you did. + Test it, save it as scripts/hn_top.py, and save reuse instructions in + scripts/README.md. + `, + workspaceId: workspace.id, +}); +const first = await client.runs.waitForCompletion(created.id); +console.log(first.result); ``` -## Cloud dashboard +Later, start a new run in the same workspace and tell the agent to use the saved script: -You can also manage workspaces from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=workspaces). +```python Python +created = await client.runs.create( + "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspace_id=workspace.id, +) +rerun = await client.runs.wait_for_completion(created.id) +print(rerun.result) +``` +```typescript TypeScript +const created = await client.runs.create({ + task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", + workspaceId: workspace.id, +}); +const rerun = await client.runs.waitForCompletion(created.id); +console.log(rerun.result); +``` - Deleting a workspace permanently removes all its files. This cannot be undone. +This pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. + V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script +# Human in the loop +Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -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 a human checkpoint for approvals, payments, complex authentication, or reviewing work before the agent continues. -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. +The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. ```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +from browser_use_sdk.v4 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), +created = await client.runs.create( + "Find noise-cancelling headphones on Amazon and stop before selecting a product" ) +await client.runs.wait_for_completion(created.id) + +events = await client.runs.events(created.id, limit=100) +ready = next(event for event in events.events if event.type == "browser.ready") +live_url = ready.data["live_view_url"] +print(f"Open this live browser: {live_url}") + +input("Press Enter after selecting a product...") -# 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), +follow_up = await client.runs.create( + "Get the selected product's name, price, and rating", + session_id=created.session_id, ) +result = await client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```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. - - -# Human in the loop -Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop - - -## 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 - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# 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, -) -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...") - -# 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) - -# Clean up -await client.sessions.stop(session.id) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import * as readline from "readline"; +import { BrowserUse } from "browser-use-sdk/v4"; +import * as readline from "node:readline/promises"; const client = new BrowserUse(); +const created = await client.runs.create({ + task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +}); +await client.runs.waitForCompletion(created.id); -// 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); +const events = await client.runs.events(created.id, { limit: 100 }); +const ready = events.events.find((event) => event.type === "browser.ready"); +const liveUrl = ready?.data.live_view_url; +console.log(`Open this live browser: ${liveUrl}`); -// 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), -); +await rl.question("Press Enter after selecting a product..."); 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); +const followUp = await client.runs.create({ + task: "Get the selected product's name, price, and rating", + sessionId: created.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); ``` +The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. + + Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. + +See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) for the event response. # Introduction Stealth @@ -1080,12 +712,12 @@ 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", -}, + custom_proxy={ + "host": "proxy.example.com", + "port": 8080, + "username": "user", + "password": "pass", + }, ) ``` ```typescript TypeScript @@ -1094,10 +726,10 @@ 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", + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", }, }); ``` @@ -1194,14 +826,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse client = AsyncBrowserUse() result = await client.run( -"Check how many GitHub stars browser-use has", -enable_recording=True, + "Check how many GitHub stars browser-use has", + enable_recording=True, ) # Waits up to 15s for recording to be ready. Returns [] if no browser was opened. urls = await client.sessions.wait_for_recording(result.id) for url in urls: -print(url) # presigned MP4 download URL + print(url) # presigned MP4 download URL ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v3"; @@ -1260,11 +892,11 @@ from playwright.async_api import async_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" async with async_playwright() as p: -browser = await p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await browser.close() + browser = await p.chromium.connect_over_cdp(WSS_URL) + page = browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -1304,11 +936,11 @@ from playwright.sync_api import sync_playwright WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" with sync_playwright() as p: -browser = p.chromium.connect_over_cdp(WSS_URL) -page = browser.contexts[0].pages[0] -page.goto("https://example.com") -print(page.title()) -browser.close() + browser = 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. @@ -1340,11 +972,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com print(browser.live_url) # https://live.browser-use.com?wss=... async with async_playwright() as p: -pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) -page = pw_browser.contexts[0].pages[0] -await page.goto("https://example.com") -print(await page.title()) -await pw_browser.close() + pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url) + page = pw_browser.contexts[0].pages[0] + await page.goto("https://example.com") + print(await page.title()) + await pw_browser.close() await client.browsers.stop(browser.id) ``` @@ -1437,7 +1069,7 @@ profile = await client.profiles.create(name="work-account") # List all response = await client.profiles.list() for p in response.items: -print(p.id, p.name) + print(p.id, p.name) # Search by name response = await client.profiles.list(query="user-id-1") @@ -1603,8 +1235,8 @@ 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, + "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 @@ -1612,8 +1244,8 @@ 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, + "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) @@ -1662,14 +1294,14 @@ 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 + """ + 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) ``` @@ -1719,16 +1351,16 @@ client = AsyncBrowserUse() 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: + 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() + import pyotp + totp = pyotp.TOTP("{totp_secret}") + code = totp.now() -Enter the generated code. -""", + Enter the generated code. + """, ) print(result.output) ``` @@ -1772,6 +1404,187 @@ Use **Agent Mail** (enabled by default). For end-client scenarios, have them for Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed. +# Claude Code +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code + + +[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 +``` + +**2. Verify the installation** + +```bash +browser-use doctor +``` + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install +``` + +**4. Authenticate for cloud browsers** + +Sign up at [cloud.browser-use.com](https://cloud.browser-use.com), then authenticate: + +```bash +browser-use auth login +``` + +Or let Claude Code provision a free API key itself — see [Agent Self-Registration](#agent-self-registration) below. + +**5. Use it** + +Claude Code uses its bash tool to run CLI commands directly: + +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` + +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). + +## Agent Self-Registration + +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 +uv tool install browser-use +browser-use skill install +``` + +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: + +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +### 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. + +## 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](https://docs.browser-use.com/cloud/guides/x402). + + +# Claude Managed Agents +Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents + + +[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. + +The sandbox can't run a local browser, so the agent starts a named Browser Use Cloud browser and drives it with `browser-use <<'PY'` Python snippets. + +## 1. Create an environment + +Pre-install the CLI so it's ready at session start (no runtime install). + +```yaml +name: browser-env +config: + type: cloud + packages: + pip: + - browser-use + networking: + type: limited + allowed_hosts: ["*.browser-use.com"] + allow_package_managers: true +``` + +## 2. Create a credential vault + +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). + +| Field | Value | +| ----- | --------------------- | +| Type | Environment variable | +| Name | `BROWSER_USE_API_KEY` | +| Value | `bu_...` | + +## 3. Create the agent + +Tell it to use the CLI in cloud mode. + +```yaml +name: browser agent +model: + id: claude-opus-4-8 +description: Drives a stealth cloud browser with the Browser Use CLI. +system: | + You are a browser agent. Use the `browser-use` CLI to complete web tasks. + Never launch a local browser in this sandbox. Start a named cloud browser: + browser-use <<'PY' + start_remote_daemon("managed") + PY + Then run browser work through the same name: + BU_NAME=managed browser-use <<'PY' + new_tab("https://example.com") + print(page_info()) + PY + Your BROWSER_USE_API_KEY is in the environment; never print it. +tools: + - type: agent_toolset_20260401 # shell access so the agent can run the CLI + default_config: + enabled: true + permission_policy: + type: always_allow +``` + +## 4. Start a session and send a task + +The Console only observes; kick the agent off with a `user.message` event. + +```bash +curl -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: managed-agents-2026-04-01" \ + -H "content-type: application/json" \ + -d '{"events":[{"type":"user.message","content":[{"type":"text", + "text":"Get the top 5 Hacker News stories with their links."}]}]}' +``` + +## 5. Watch it run + +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 @@ -1799,16 +1612,16 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: ```json5 { browser: { -enabled: true, -defaultProfile: "browser-use", -remoteCdpTimeoutMs: 3000, -remoteCdpHandshakeTimeoutMs: 5000, -profiles: { - "browser-use": { - cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", - color: "#ff750e", - }, -}, + enabled: true, + defaultProfile: "browser-use", + remoteCdpTimeoutMs: 3000, + remoteCdpHandshakeTimeoutMs: 5000, + profiles: { + "browser-use": { + cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us", + color: "#ff750e", + }, + }, }, } ``` @@ -1846,7 +1659,7 @@ The Browser Use CLI is a standalone tool that gives any OpenClaw agent browser a **1. Install the CLI** ```bash -curl -fsSL https://browser-use.com/cli/install.sh | bash +uv tool install browser-use ``` **2. Verify the installation** @@ -1863,40 +1676,172 @@ Paste this setup prompt into your OpenClaw agent: Install or upgrade browser-use with `uv tool install --python 3.12 --upgrade --force 'browser-use @ git+https://github.com/browser-use/browser-use.git'`, run `browser-use skill install`, and connect it to my browser. Follow https://github.com/browser-use/browser-use if setup or connection fails. ``` -Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to navigate pages, click elements, fill forms, take screenshots, extract data, and more. The skill file teaches the agent the full command set. +Once the skill is loaded, OpenClaw agents can use the `browser-use` CLI to drive pages through Browser Harness and Python helpers. -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). +For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server +# Hermes Agent +Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent -``` -https://api.browser-use.com/v3/mcp -``` +[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. -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). +Two ways to set it up: configure Browser Use as Hermes's cloud browser backend, or install the Browser Use CLI and let Hermes drive it directly. -## Claude Code +## 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. + +### Setup + +**1. Get your API key** + +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). + +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. + +**2. Configure Hermes** + +Run the setup wizard: ```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp +hermes setup tools ``` -## Claude Desktop +Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. -Add to `claude_desktop_config.json`: +Or configure manually — add your key to `~/.hermes/.env`: + +```bash +BROWSER_USE_API_KEY=your_key_here +``` + +And set the provider in `~/.hermes/config.yaml`: + +```yaml +browser: + cloud_provider: browser-use +``` + +**3. Use it** + +Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: + +``` +> Find the top trending repositories on GitHub today and summarize them +``` + +## Option 2: Browser Use CLI + +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. + +### Setup + +**1. Install the CLI** + +```bash +uv tool install browser-use +``` + +**2. Verify the installation** + +```bash +browser-use doctor +``` + +**3. Register the skill** + +Register the Browser Use skill with the installed CLI: + +```bash +browser-use skill install +``` + +Or ask Hermes directly in chat to install it. + +**4. Authenticate for cloud browsers** + +Authenticate with your API key: + +```bash +browser-use auth login +``` + +Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. + +**5. Use it** + +Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: + +``` +> Use browser-use to open github.com/trending and summarize the top repos +``` + +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). + +## Agent Self-Registration + +Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. + +Install the Browser Use CLI and skill: + +```bash +uv tool install browser-use +browser-use skill install +``` + +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** + +For the cloud browser backend (Option 1): + +```bash +hermes config set BROWSER_USE_API_KEY +``` + +For CLI mode (Option 2), put the key in the agent's shell environment: + +```bash +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +### 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. + + +# 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" - } -} + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1908,12 +1853,12 @@ 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" - } -} + "browser-use": { + "url": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1925,12 +1870,12 @@ 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" - } -} + "browser-use": { + "serverUrl": "https://api.browser-use.com/v3/mcp", + "headers": { + "x-browser-use-api-key": "YOUR_API_KEY" + } + } } } ``` @@ -1968,10 +1913,10 @@ Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.b "type": "agent.task.status_update", "timestamp": "2025-01-15T10:30:00Z", "payload": { -"task_id": "task_abc123", -"session_id": "session_xyz", -"status": "idle", -"metadata": {} + "task_id": "task_abc123", + "session_id": "session_xyz", + "status": "idle", + "metadata": {} } } ``` @@ -1992,17 +1937,17 @@ 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) + # 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"; @@ -2010,12 +1955,12 @@ 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 Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); } return obj; } @@ -2030,102 +1975,420 @@ function verifyWebhook(body: string, signature: string, timestamp: string, secre } ``` -## Example: Express webhook handler +## Example: Express webhook handler + +```typescript +import express from "express"; +import { createHmac, timingSafeEqual } from "crypto"; + +const app = express(); +app.use(express.raw({ type: "application/json" })); + +const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; + +function sortKeys(obj: unknown): unknown { + if (Array.isArray(obj)) return obj.map(sortKeys); + if (obj !== null && typeof obj === "object") { + return Object.keys(obj as object) + .sort() + .reduce((acc, key) => { + (acc as Record)[key] = sortKeys((obj as Record)[key]); + return acc; + }, {} as Record); + } + return obj; +} + +app.post("/webhook", (req, res) => { + const signature = req.headers["x-browser-use-signature"] as string; + const timestamp = req.headers["x-browser-use-timestamp"] as string; + + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { + return res.status(401).send("Request too old"); + } + + const body = req.body.toString(); + const payload = JSON.parse(body); + const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; + const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); + + if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { + return res.status(401).send("Invalid signature"); + } + + if (payload.type === "agent.task.status_update") { + const { task_id, status, session_id } = payload.payload; + console.log(`Task ${task_id} is now ${status}`); + } + + res.status(200).send("OK"); +}); + +app.listen(3000); +``` + +## Example: FastAPI webhook handler + +```python +from fastapi import FastAPI, Request, HTTPException +import hashlib +import hmac +import json +import os +import time + +app = FastAPI() + +WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] + +@app.post("/webhook") +async def handle_webhook(request: Request): + body = await request.body() + signature = request.headers.get("x-browser-use-signature", "") + timestamp = request.headers.get("x-browser-use-timestamp", "") + + # Reject requests older than 5 minutes + try: + ts = int(timestamp) + except (ValueError, TypeError): + raise HTTPException(status_code=401, detail="Invalid timestamp") + if abs(time.time() - ts) > 300: + raise HTTPException(status_code=401, detail="Request too old") + + payload = json.loads(body) + message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" + expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() + + if not hmac.compare_digest(expected, signature): + raise HTTPException(status_code=401, detail="Invalid signature") + + if payload["type"] == "agent.task.status_update": + task_id = payload["payload"]["task_id"] + status = payload["payload"]["status"] + print(f"Task {task_id} is now {status}") + + return {"status": "ok"} +``` + + For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. + + +# x402 (pay-per-request) +Source: https://docs.browser-use.com/cloud/guides/x402 + + + + +[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. + +x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. + + +**New to crypto?** Here's the gist: + +- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. +- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. +- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. +- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. + + +**Three ways to start, ranked by laziness:** + +One command. Claude does the wallet setup, funding walkthrough, and +verification for you. +One line in your Python or TypeScript app. Bring your own wallet. +Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. + +## Claude Code quickstart + +The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: + +```bash +npx skills add https://github.com/browser-use/browser-use --skill x402 +``` + +Then in Claude Code: + +``` +> /x402 +``` + +Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. + + Already have a Browser Use Cloud account? The skill detects this and switches + to **top-up mode**, adding credits to that existing account instead of + creating a new, wallet-keyed one. + +## SDK quickstart + +The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. + +```bash Python +pip install "browser-use-sdk[x402]" +``` +```bash TypeScript +npm install browser-use-sdk @x402/fetch @x402/evm viem +``` + +```python Python +import asyncio +from browser_use_sdk.v3 import AsyncBrowserUse + +async def main(): + client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base + result = await client.run("Go to example.com and tell me the heading.") + print(result.output) + +asyncio.run(main()) +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base +const result = await client.run("Go to example.com and tell me the heading."); +console.log(result.output); +``` + +Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: + +```python Python +client = AsyncBrowserUse() # auto-detects from env +``` +```typescript TypeScript +const client = new BrowserUse(); // auto-detects from env +``` + + Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. + +## Raw HTTP quickstart + +Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): + +```python +import asyncio + +from x402 import x402Client +from x402.http.clients import x402HttpxClient +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account + +async def main(): + client = x402Client() + register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) + + async with x402HttpxClient(client, timeout=120.0) as http: + response = await http.post( + "https://x402.api.browser-use.com/api/v3/sessions", + json={"task": "..."}, + ) + print(response.status_code, response.text[:500]) + +asyncio.run(main()) +``` + +`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. + +## What you need + +- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app +- **USD Coin (USDC) on Base mainnet** +- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) + +You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. + + +## Pricing and credits + +Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** + + **Mid-task drain still terminates the task.** Browser Use sessions run on a + worker that doesn't see x402, so once a long-running task starts and burns + through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause + and wait for the next x402 payment. The `$5` default exists so most tasks + complete without hitting this; for expensive models (e.g. Opus) or long + sessions, pre-fund with multiple requests before kicking off the task. + +See the [pricing page](https://browser-use.com/pricing) for model and browser costs. + +## Topping up an existing account + +If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: + +```python Python +import asyncio + +from browser_use_sdk.v3 import AsyncBrowserUse + +client = AsyncBrowserUse( + api_key="bu_...", # existing API key getting topped up + x402_private_key="0x...", # wallet that pays + base_url="https://x402.api.browser-use.com/api/v3", +) +async def main(): + result = await client.run("...") # $5 USDC charged, credited to the API key's project + print(result.output) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v3"; + +const client = new BrowserUse({ + apiKey: "bu_...", + x402PrivateKey: "0x...", + baseUrl: "https://x402.api.browser-use.com/api/v3", +}); +const result = await client.run("..."); +``` + +When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: + +- Agents that ran out of free-tier credits and need to keep going +- Adding credits via crypto when you already have a regular Browser Use account +- Multi-wallet setups funding one shared account + +## Checking your credit balance + +When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. + +This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. + +To check how much credit that account has left, use the method below: + +```python Python +import asyncio + +from browser_use_sdk.v3 import get_wallet_balance + +async def main(): + balance = await get_wallet_balance("0x...") # same wallet private key you pay with + print(balance["total_credits_usd"]) + +asyncio.run(main()) + +``` + +```typescript TypeScript +import { getWalletBalance } from "browser-use-sdk/v3"; + +const balance = await getWalletBalance("0x..."); // same wallet private key you pay with +console.log(balance.total_credits_usd); +``` + +The response contains: + +| Field | Description | +| ------------------------ | ------------------------------------------------------------------------------- | +| `wallet` | The wallet address (lowercased) | +| `project_id` | The account (project) tied to your wallet that the credits live in | +| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | +| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | + + This is for accounts created from a wallet (the default x402 mode). If you're + [topping up an existing account](#topping-up-an-existing-account), check that + account's balance the normal way with your API key via + `client.billing.account()`. A wallet that has never paid yet has no account, + so the call returns `404` until the first payment. + + The SDK signs a fixed, server-defined message + ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with + Ethereum" mechanism) with your wallet's private key. The signature proves you + control the address without moving any funds. The server recovers the signer, + matches it to the wallet's project, and returns the balance. + +## How it works + +Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. + +A bit more detail: + +1. Your code makes a request (e.g. "run this task"). +2. The SDK auto-signs the payment from your wallet and resends the request. +3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. +4. We run your task and send back the result. -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; +## Wallet setup -const app = express(); -app.use(express.raw({ type: "application/json" })); +If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; +Get the [MetaMask browser extension](https://metamask.io) via the official +site only. Create a new wallet, save the seed phrase somewhere offline, set +a password. +By default, most wallets only show Ethereum. You need to add **Base** (the +network we accept payments on) so your wallet can hold USDC there. +Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and +pay with credit card, bank, etc. The USDC lands directly in your wallet. +In MetaMask: click the account menu → **Account details** → **Private keys** +→ enter your password → copy. That string (starts with `0x`) is your +`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in +their account settings. -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; -} + Wallets hold real money, and anyone with the private key can drain it. Be + careful with your keys. -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; +## Advanced: bring your own x402 client - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { -return res.status(401).send("Request too old"); - } +For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: - 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"); +```python Python +from x402 import x402Client +from x402.mechanisms.evm import EthAccountSigner +from x402.mechanisms.evm.exact.register import register_exact_evm_client +from eth_account import Account +from browser_use_sdk.v3 import AsyncBrowserUse - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { -return res.status(401).send("Invalid signature"); - } +x402 = x402Client() +register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) +client = AsyncBrowserUse(x402=x402) - 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"); -}); +```typescript TypeScript +import { x402Client } from "@x402/fetch"; +import { ExactEvmScheme } from "@x402/evm"; +import { privateKeyToAccount } from "viem/accounts"; +import { BrowserUse } from "browser-use-sdk/v3"; -app.listen(3000); +const x402 = new x402Client(); +x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); +const client = new BrowserUse({ x402 }); ``` -## Example: FastAPI webhook handler +## Troubleshooting -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time +Two likely causes: -app = FastAPI() +- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. +- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). -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", "") + You haven't installed the optional x402 deps. Run `pip install + "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` + (TypeScript). -# 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") + We verified your payment request but couldn't credit your project, so we + deliberately did not settle on-chain. No USDC was moved, so just retry. This + is rare. -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() + Wait a few seconds. Settlement and credit grant happen in the same request, + but the response may be sent before the credit grant fully commits. If credits + still show `$0` after a few minutes, contact support with your wallet address. + (Conversely, if a payment settles but the request itself then fails, we + automatically reclaim the credits so you aren't charged for nothing.) -if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") +`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. -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}") +## Related -return {"status": "ok"} -``` +- [x402 protocol spec](https://www.x402.org) +- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use +- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) - 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. + # n8n @@ -2223,8 +2486,8 @@ import { client } from "./api"; export async function createSession() { const session = await client.sessions.create({ -keepAlive: true, -enableRecording: true, + keepAlive: true, + enableRecording: true, }); return { id: session.id, liveUrl: session.liveUrl, status: session.status }; } @@ -2241,7 +2504,7 @@ async function handleSend(message: string) { const session = await createSession(); router.push( -`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` + `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` ); } ``` @@ -2257,7 +2520,7 @@ const streamTask = useCallback(async (task: string) => { const run = client.run(task, { sessionId }); for await (const msg of run) { -setMessages((prev) => [...prev, msg]); + setMessages((prev) => [...prev, msg]); } // Iterator done — task reached terminal state @@ -2301,7 +2564,7 @@ useEffect(() => { if (!isTerminal) return; client.sessions.waitForRecording(sessionId).then((urls) => { -if (urls.length) setRecordingUrls(urls); + if (urls.length) setRecordingUrls(urls); }); }, [isTerminal, sessionId]); ``` @@ -2329,23 +2592,23 @@ The session page consumes everything through a context provider: ```typescript session/[id]/page.tsx function SessionPage() { const { session, turns, isBusy, isTerminal, recordingUrls, sendMessage, stopTask } = -useSession(); + useSession(); return ( -
- {/* Chat column */} -
- - -
- - {/* Live browser view — liveUrl available from session creation */} - -
+
+ {/* Chat column */} +
+ + +
+ + {/* Live browser view — liveUrl available from session creation */} + +
); } ``` @@ -2362,6 +2625,116 @@ useSession(); | `client.sessions.waitForRecording()` | Get MP4 recording URLs | +# Agent Sign Up for Browser Use +Source: https://docs.browser-use.com/cloud/agent-signup + + +An AI agent can create its own free Browser Use account without a human opening the dashboard. This is useful when an agent has terminal or HTTP access and needs a Browser Use API key before it can run cloud browser tasks. + +The flow is a Browser Use agent challenge: the agent requests a challenge, solves the math problem, verifies the answer, and receives an API key. + +## REST flow + +### 1. Request a challenge + +```bash +curl -X POST https://api.browser-use.com/cloud/signup \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +Request body, optional (include a user email/name if available): + +```json +{ + "email": "user@example.com", + "name": "User Name" +} +``` + +Response: + +```json +{ + "challenge_id": "uuid", + "challenge_text": "..." +} +``` + +### 2. Solve the challenge + +Read `challenge_text` and solve the math problem. Return the answer as a string with two decimal places, for example `"144.00"`. + +### 3. Verify the answer + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/verify \ + -H "Content-Type: application/json" \ + -d '{"challenge_id":"uuid","answer":"144.00"}' +``` + +Request body: + +```json +{ + "challenge_id": "uuid", + "answer": "144.00" +} +``` + +Response: + +```json +{ + "api_key": "bu_..." +} +``` + +Use the returned key for Browser Use Cloud API requests. + +For example, create a browser session: + +```bash +curl -X POST https://api.browser-use.com/api/v3/browsers \ + -H "X-Browser-Use-API-Key: bu_..." \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +See the [Create Browser Session API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). + +## Claim the account + +If a human wants to see the agent-created account in the dashboard later, the agent can create a claim link: + +```bash +curl -X POST https://api.browser-use.com/cloud/signup/claim \ + -H "X-Browser-Use-API-Key: bu_..." +``` + +Response: + +```json +{ + "claim_url": "https://..." +} +``` + +The claim URL is valid for 1 hour. + +## CLI usage + +Agents with shell access can use the Browser Use CLI after the REST flow returns an API key: + +```bash +uv tool install browser-use +export BROWSER_USE_API_KEY=bu_... +browser-use auth status +``` + +Replace `bu_...` with the key returned by the REST flow. + + # Grow Therapy provider search Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare @@ -2398,28 +2771,28 @@ const client = new BrowserUse(); ```python Python class Provider(BaseModel): -name: str -title: str -specialties: list[str] -insurance_plans: list[str] -rating: float | None = None -next_available: str | None = None + name: str + title: str + specialties: list[str] + insurance_plans: list[str] + rating: float | None = None + next_available: str | None = None class ProviderSearch(BaseModel): -providers: list[Provider] -total_found: int | None = None -location: str -specialty: str + providers: list[Provider] + total_found: int | None = None + location: str + specialty: str ``` ```typescript TypeScript const ProviderSearch = z.object({ providers: z.array(z.object({ -name: z.string(), -title: z.string(), -specialties: z.array(z.string()), -insurancePlans: z.array(z.string()), -rating: z.number().nullable(), -nextAvailable: z.string().nullable(), + name: z.string(), + title: z.string(), + specialties: z.array(z.string()), + insurancePlans: z.array(z.string()), + rating: z.number().nullable(), + nextAvailable: z.string().nullable(), })), totalFound: z.number().nullable(), location: z.string(), @@ -2440,19 +2813,19 @@ const workspace = await client.workspaces.create({ name: "grow-therapy-search" } ```python Python result = await client.run( -"Go to growtherapy.com and search for therapists in {{New York}} " -"who specialize in {{anxiety}} and accept insurance. " -"Return the first 5 provider profiles as JSON.", -workspace_id=str(workspace.id), -output_schema=ProviderSearch, + "Go to growtherapy.com and search for therapists in {{New York}} " + "who specialize in {{anxiety}} and accept insurance. " + "Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, ) for p in result.output.providers: -print(f"{p.name} ({p.title})") -print(f" Specialties: {', '.join(p.specialties)}") -print(f" Rating: {p.rating}") -print(f" Next available: {p.next_available}") -print() + print(f"{p.name} ({p.title})") + print(f" Specialties: {', '.join(p.specialties)}") + print(f" Rating: {p.rating}") + print(f" Next available: {p.next_available}") + print() ``` ```typescript TypeScript const result = await client.run( @@ -2479,16 +2852,16 @@ locations = ["Los Angeles", "Chicago", "Houston", "Miami"] specialties = ["depression", "trauma", "ADHD"] for location in locations: -for specialty in specialties: - result = await client.run( - f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " - f"who specialize in {{{{{specialty}}}}} and accept insurance. " - f"Return the first 5 provider profiles as JSON.", - workspace_id=str(workspace.id), - output_schema=ProviderSearch, - ) - count = len(result.output.providers) - print(f"{location} / {specialty}: {count} providers found") + for specialty in specialties: + result = await client.run( + f"Go to growtherapy.com and search for therapists in {{{{{location}}}}} " + f"who specialize in {{{{{specialty}}}}} and accept insurance. " + f"Return the first 5 provider profiles as JSON.", + workspace_id=str(workspace.id), + output_schema=ProviderSearch, + ) + count = len(result.output.providers) + print(f"{location} / {specialty}: {count} providers found") ``` ```typescript TypeScript const locations = ["Los Angeles", "Chicago", "Houston", "Miami"]; @@ -2496,13 +2869,13 @@ const specialties = ["depression", "trauma", "ADHD"]; for (const location of locations) { for (const specialty of specialties) { -const result = await client.run( - `Go to growtherapy.com and search for therapists in {{${location}}} ` + - `who specialize in {{${specialty}}} and accept insurance. ` + - `Return the first 5 provider profiles as JSON.`, - { workspaceId: workspace.id, schema: ProviderSearch }, -); -console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); + const result = await client.run( + `Go to growtherapy.com and search for therapists in {{${location}}} ` + + `who specialize in {{${specialty}}} and accept insurance. ` + + `Return the first 5 provider profiles as JSON.`, + { workspaceId: workspace.id, schema: ProviderSearch }, + ); + console.log(`${location} / ${specialty}: ${result.output.providers.length} providers`); } } ``` @@ -2532,19 +2905,27 @@ 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) +created = await client.runs.create("Go to example.com") +await client.runs.wait_for_completion(created.id) +events = await 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,23 +2939,24 @@ 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 vs v3 vs 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 new agent integrations.** It is designed for long-horizon work: -- **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 queued and interrupting 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. +V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. ```python -# v3 (recommended) -from browser_use_sdk.v3 import AsyncBrowserUse +# v4 (recommended for new agent runs) +from browser_use_sdk.v4 import AsyncBrowserUse -# v2 (simple browser-only tasks) -from browser_use_sdk.v2 import AsyncBrowserUse +# v3 (existing session-based integrations) +from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 ``` @@ -2587,7 +2969,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 +3002,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 +3043,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 +3066,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 ``` @@ -2767,8 +3148,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 +3170,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) ``` @@ -2862,9 +3243,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 +3256,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 +3267,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"], }, ); ``` @@ -2924,9 +3305,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 +3317,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,28 +3329,85 @@ For SSO/OAuth redirects, include all domains in the auth flow: ```python Python result = await client.run( -"Log into the company portal and download the Q4 report", -secrets={ - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowed_domains=["portal.example.com", "*.okta.com"], + "Log into the company portal and download the Q4 report", + secrets={ + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowed_domains=["portal.example.com", "*.okta.com"], ) ``` ```typescript TypeScript const result = await client.run( "Log into the company portal and download the Q4 report", { -secrets: { - "portal.example.com": "user@company.com:password123", - "okta.com": "user@company.com:password123", -}, -allowedDomains: ["portal.example.com", "*.okta.com"], + secrets: { + "portal.example.com": "user@company.com:password123", + "okta.com": "user@company.com:password123", + }, + allowedDomains: ["portal.example.com", "*.okta.com"], }, ); ``` +# API Reference +Source: https://docs.browser-use.com/cloud/api-v4-overview + + +## Authentication + +All requests require an API key in the `X-Browser-Use-API-Key` header: + +``` +X-Browser-Use-API-Key: bu_your_key_here +``` + +Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. + +## Base URL + +``` +https://api.browser-use.com/api/v4 +``` + +## The core loop + +Create a run, poll its status until terminal, then fetch the full result. `status` is a cheap indexed lookup — poll it, not the full run. + +```bash Create a run +curl -X POST https://api.browser-use.com/api/v4/runs \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"task": "Find the top 3 trending repos on GitHub today"}' +``` + +```bash Poll status until completed | failed | cancelled (replace RUN_ID) +curl https://api.browser-use.com/api/v4/runs/RUN_ID/status \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +```bash Fetch the full run once it's terminal +curl https://api.browser-use.com/api/v4/runs/RUN_ID \ + -H "X-Browser-Use-API-Key: bu_your_key_here" +``` + +## Sessions and follow-ups + +A run belongs to a session (a conversation). Send a follow-up message to a session's queue — it runs as the next turn, or immediately with `interrupt: true`: + +```bash Queue a follow-up (replace SESSION_ID) +curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ + -H "X-Browser-Use-API-Key: bu_your_key_here" \ + -H "Content-Type: application/json" \ + -d '{"text": "Now open the top result", "interrupt": false}' +``` + +## SDKs + +The [Cloud SDK 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 Reference Source: https://docs.browser-use.com/cloud/api-reference @@ -3046,66 +3484,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 8b1c30c1..05bab8ad 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,15 +1,14 @@ # Browser Use Cloud SDK -> 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 is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (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. +**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. 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` @@ -23,18 +22,17 @@ 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. - [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. +- [Introduction](https://docs.browser-use.com/cloud/agent/quickstart): Run a long-horizon browser agent with one task and a few lines of code. +- [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, then validate the V4 run result in your application. +- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Continue the same V4 conversation, workspace, and browser. +- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Poll V4 run events incrementally to monitor progress or build a custom UI. +- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Give a V4 run input files and retrieve files the agent creates. +- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save and test a reusable script, then run it again from the same workspace. +- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the V4 live browser, let a person take over, then continue the same session. ## 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. @@ -52,12 +50,19 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## 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. +- [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. - [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. - [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. +- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. - [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. +## 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. +- [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. - [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) @@ -67,70 +72,11 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [1Password & 2FA](https://docs.browser-use.com/cloud/guides/1password): Auto-fill passwords and TOTP codes from 1Password during agent tasks. - [Secrets](https://docs.browser-use.com/cloud/guides/secrets): Pass domain-scoped credentials to the agent securely. +## API v4 +- [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. + ## API v3 - [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. - -## Browser Use Agent Challenge to Get a Free Account - -AI agents can create a free Browser Use account autonomously via a challenge-response flow at `https://api.browser-use.com`. - -Do this to get an API key (no human required): -1. `POST https://api.browser-use.com/cloud/signup` — request a challenge. - -Request body, optional (include a user email/name if available): -```json -{ - "email": "user@example.com", - "name": "User Name" -} -``` - -Response: -```json -{ - "challenge_id": "uuid", - "challenge_text": "..." -} -``` - -2. Solve the math problem in `challenge_text` — answer as a string with 2 decimal places, e.g. `"144.00"` - -3. `POST https://api.browser-use.com/cloud/signup/verify` with `challenge_id` + `answer`. - -Request body: -```json -{ - "challenge_id": "uuid", - "answer": "144.00" -} -``` - -Response: -```json -{ - "api_key": "bu_..." -} -``` - -4. Use `X-Browser-Use-API-Key: bu_...` on Browser Use Cloud API requests. For example, create a browser session (docs: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session): - -```bash -curl -X POST https://api.browser-use.com/api/v3/browsers \ - -H "X-Browser-Use-API-Key: bu_..." \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -To let a human claim the account later: `POST https://api.browser-use.com/cloud/signup/claim` with `X-Browser-Use-API-Key: bu_...`. - -Response: -```json -{ - "claim_url": "https://..." -} -``` - -The claim URL is valid for 1 hour. diff --git a/docs/openapi/v4.json b/docs/openapi/v4.json index 0a15025f..ec1f6424 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" diff --git a/snapshots/v4.json b/snapshots/v4.json index 25c4488d..a6f769c3 100644 --- a/snapshots/v4.json +++ b/snapshots/v4.json @@ -3109,6 +3109,7 @@ "minimax-m3", "claude-opus-4.7", "claude-opus-4.8", + "claude-opus-5", "claude-fable-5", "claude-sonnet-5", "gpt-5.5", @@ -3753,7 +3754,7 @@ "anyOf": [ { "type": "string", - "maxLength": 255 + "maxLength": 100 }, { "type": "null" From 57116b1f133f87510f882d7525b36b34e37282a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:08:22 -0700 Subject: [PATCH 02/15] docs: make cloud SDK API v4-first --- browser-use-node/src/v4.ts | 2 +- browser-use-node/src/v4/resources/runs.ts | 11 +- browser-use-node/tests/v4.test.ts | 20 + .../src/browser_use_sdk/v4/resources/runs.py | 6 +- browser-use-python/tests/test_v4.py | 27 + docs/cloud/agent-signup.mdx | 8 +- docs/cloud/agent/cache-script.mdx | 77 +- docs/cloud/agent/follow-up-tasks.mdx | 74 - docs/cloud/agent/human-in-the-loop.mdx | 76 +- docs/cloud/agent/models.mdx | 46 +- docs/cloud/agent/observability.mdx | 43 + docs/cloud/agent/quickstart.mdx | 64 +- docs/cloud/agent/sessions.mdx | 49 + docs/cloud/agent/streaming.mdx | 85 - docs/cloud/agent/structured-output.mdx | 64 +- docs/cloud/agent/workspaces.mdx | 110 +- docs/cloud/browser/live-preview.mdx | 159 +- .../browser/playwright-puppeteer-selenium.mdx | 110 +- docs/cloud/browser/proxies.mdx | 98 +- docs/cloud/browser/stealth.mdx | 4 +- docs/cloud/faq.mdx | 18 +- docs/cloud/guides/2fa.mdx | 286 +- docs/cloud/guides/authentication.mdx | 110 +- docs/cloud/guides/profile-sync.mdx | 30 +- docs/cloud/guides/x402.mdx | 4 +- .../cloud/images/v4-agent-overview.excalidraw | 394 +++ docs/cloud/images/v4-agent-overview.png | Bin 0 -> 114314 bytes docs/cloud/images/v4-agent-overview.svg | 38 + docs/cloud/images/v4-sessions.excalidraw | 362 +++ docs/cloud/images/v4-sessions.png | Bin 0 -> 85025 bytes docs/cloud/images/v4-sessions.svg | 33 + docs/cloud/images/v4-workspaces.excalidraw | 476 +++ docs/cloud/images/v4-workspaces.png | Bin 0 -> 113738 bytes docs/cloud/images/v4-workspaces.svg | 41 + docs/cloud/llms-full.txt | 2713 ++++------------- docs/cloud/llms.txt | 49 +- docs/cloud/quickstart.mdx | 76 +- .../tutorials/integrations/claude-code.mdx | 23 - docs/docs.json | 40 +- docs/generate-llms-txt.sh | 31 +- docs/llms-full.txt | 2713 ++++------------- docs/llms.txt | 49 +- 42 files changed, 3051 insertions(+), 5568 deletions(-) delete mode 100644 docs/cloud/agent/follow-up-tasks.mdx create mode 100644 docs/cloud/agent/observability.mdx create mode 100644 docs/cloud/agent/sessions.mdx delete mode 100644 docs/cloud/agent/streaming.mdx create mode 100644 docs/cloud/images/v4-agent-overview.excalidraw create mode 100644 docs/cloud/images/v4-agent-overview.png create mode 100644 docs/cloud/images/v4-agent-overview.svg create mode 100644 docs/cloud/images/v4-sessions.excalidraw create mode 100644 docs/cloud/images/v4-sessions.png create mode 100644 docs/cloud/images/v4-sessions.svg create mode 100644 docs/cloud/images/v4-workspaces.excalidraw create mode 100644 docs/cloud/images/v4-workspaces.png create mode 100644 docs/cloud/images/v4-workspaces.svg diff --git a/browser-use-node/src/v4.ts b/browser-use-node/src/v4.ts index c5a163e6..8f136114 100644 --- a/browser-use-node/src/v4.ts +++ b/browser-use-node/src/v4.ts @@ -5,6 +5,7 @@ export { BrowserUseError } from "./core/errors.js"; export { Runs } from "./v4/resources/runs.js"; export type { + RunBrowserSettings, RunCreateRequest, RunListParams, RunEventsParams, @@ -32,7 +33,6 @@ export type RunEvent = S["RunEvent"]; export type RunEventsResponse = S["RunEventsResponse"]; export type RunAttachment = S["RunAttachment"]; export type RunAttachmentsResponse = S["RunAttachmentsResponse"]; -export type RunBrowserSettings = S["RunBrowserSettings"]; export type RunJudgeSettings = S["RunJudgeSettings"]; // Session models diff --git a/browser-use-node/src/v4/resources/runs.ts b/browser-use-node/src/v4/resources/runs.ts index bdbdb558..e24d4694 100644 --- a/browser-use-node/src/v4/resources/runs.ts +++ b/browser-use-node/src/v4/resources/runs.ts @@ -2,9 +2,18 @@ import type { HttpClient } from "../../core/http.js"; import type { components } from "../../generated/v4/types.js"; type GeneratedRunCreateRequest = components["schemas"]["RunCreateRequest"]; -export type RunCreateRequest = Omit & { +type GeneratedRunBrowserSettings = components["schemas"]["RunBrowserSettings"]; +export type RunBrowserSettings = Omit & { + /** Defaults to US when omitted. Pass null to disable the managed proxy. */ + proxyCountryCode?: GeneratedRunBrowserSettings["proxyCountryCode"]; +}; +export type RunCreateRequest = Omit< + GeneratedRunCreateRequest, + "model" | "browserSettings" +> & { /** Defaults to minimax-m3 when omitted. */ model?: GeneratedRunCreateRequest["model"]; + browserSettings?: RunBrowserSettings | null; }; type RunCreateResponse = components["schemas"]["RunCreateResponse"]; type RunSummary = components["schemas"]["RunSummary"]; diff --git a/browser-use-node/tests/v4.test.ts b/browser-use-node/tests/v4.test.ts index 7f104ffb..4dbcb269 100644 --- a/browser-use-node/tests/v4.test.ts +++ b/browser-use-node/tests/v4.test.ts @@ -52,6 +52,26 @@ describe("v4 runs.waitForCompletion", () => { }); }); + it("does not require proxyCountryCode for other browser settings", async () => { + const http = { + post: vi.fn(async () => ({ + id: RUN_ID, + sessionId: SESSION_ID, + workspaceId: WORKSPACE_ID, + status: "queued", + })), + }; + const runs = new Runs(http as any); + const request: RunCreateRequest = { + task: "Record this run", + browserSettings: { record: true }, + }; + + await runs.create(request); + + expect(http.post).toHaveBeenCalledWith("/runs", request); + }); + it("polls status until terminal, then fetches the full run once", async () => { const statuses = ["queued", "running", "completed"]; let statusCalls = 0; diff --git a/browser-use-python/src/browser_use_sdk/v4/resources/runs.py b/browser-use-python/src/browser_use_sdk/v4/resources/runs.py index a7620010..0fa6ac93 100644 --- a/browser-use-python/src/browser_use_sdk/v4/resources/runs.py +++ b/browser-use-python/src/browser_use_sdk/v4/resources/runs.py @@ -42,7 +42,11 @@ def _build_create_body( body["workspaceId"] = str(workspace_id) if browser_settings is not None: if isinstance(browser_settings, RunBrowserSettings): - body["browserSettings"] = browser_settings.model_dump(by_alias=True, exclude_none=True, mode="json") + body["browserSettings"] = browser_settings.model_dump( + by_alias=True, + exclude_unset=True, + mode="json", + ) else: body["browserSettings"] = browser_settings if attached_file_ids is not None: diff --git a/browser-use-python/tests/test_v4.py b/browser-use-python/tests/test_v4.py index 0082bbf8..87f15fc0 100644 --- a/browser-use-python/tests/test_v4.py +++ b/browser-use-python/tests/test_v4.py @@ -9,6 +9,7 @@ import httpx import pytest +from browser_use_sdk.v4 import RunBrowserSettings from browser_use_sdk.v4.resources.runs import AsyncRuns, Runs from browser_use_sdk.v4.resources.sessions import Sessions from browser_use_sdk.v4.resources.workspaces import AsyncWorkspaces, Workspaces @@ -195,6 +196,32 @@ def test_runs_create_sends_camel_case_body() -> None: assert str(created.id) == RUN_ID +def test_runs_create_preserves_explicit_null_proxy() -> None: + http = FakeSyncHttp( + [ + { + "id": RUN_ID, + "status": "queued", + "model": "minimax-m3", + "sessionId": SESSION_ID, + "workspaceId": WORKSPACE_ID, + "eventsUrl": f"https://api.browser-use.com/api/v4/runs/{RUN_ID}/events", + } + ] + ) + runs = Runs(http) # type: ignore[arg-type] + + runs.create( + "Test staging", + browser_settings=RunBrowserSettings(proxyCountryCode=None), + ) + + assert http.calls[0][2] == { + "task": "Test staging", + "browserSettings": {"proxyCountryCode": None}, + } + + def test_runs_list_cursor_pagination() -> None: http = FakeSyncHttp( [ diff --git a/docs/cloud/agent-signup.mdx b/docs/cloud/agent-signup.mdx index 62fa8b8e..0e3227b8 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 index 825f109e..d965953a 100644 --- a/docs/cloud/agent/cache-script.mdx +++ b/docs/cloud/agent/cache-script.mdx @@ -1,75 +1,28 @@ --- title: Deterministic rerun -description: "Have the agent save and test a reusable script, then run it again from the same workspace." +description: "Have the agent save, test, and reuse a script in a workspace." icon: bolt --- -For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. +Create one [workspace](/cloud/agent/workspaces) for the workflow, then use +these prompts with the same `workspace_id` / `workspaceId`. The [run +code](/cloud/agent/quickstart) stays exactly the same. -You can create the workspace in the dashboard or through the API: +## First run - -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +```text +Complete this task: get the top five Hacker News stories as JSON. -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="hn-scraper") - -created = await client.runs.create( - """ - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - """, - workspace_id=workspace.id, -) -first = await client.runs.wait_for_completion(created.id) -print(first.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "hn-scraper" }); - -const created = await client.runs.create({ - task: ` - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - `, - workspaceId: workspace.id, -}); -const first = await client.runs.waitForCompletion(created.id); -console.log(first.result); +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, start a new run in the same workspace and tell the agent to use the saved script: +## Later runs - -```python Python -created = await client.runs.create( - "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspace_id=workspace.id, -) -rerun = await client.runs.wait_for_completion(created.id) -print(rerun.result) -``` -```typescript TypeScript -const created = await client.runs.create({ - task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspaceId: workspace.id, -}); -const rerun = await client.runs.waitForCompletion(created.id); -console.log(rerun.result); +```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 pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. - - V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. - +This still starts an agent and uses tokens. The saved script gives the agent a +faster, more predictable path; it is not automatic zero-LLM execution. diff --git a/docs/cloud/agent/follow-up-tasks.mdx b/docs/cloud/agent/follow-up-tasks.mdx deleted file mode 100644 index 6686dd18..00000000 --- a/docs/cloud/agent/follow-up-tasks.mdx +++ /dev/null @@ -1,74 +0,0 @@ ---- -title: Follow-up tasks -description: "Continue the same V4 conversation, workspace, and browser." -icon: list-check ---- - -Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: - - -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() - -first = await client.runs.create( - "Go to amazon.com, search for laptops, and open the first result" -) -first_result = await client.runs.wait_for_completion(first.id) - -follow_up = await client.runs.create( - "Extract the customer reviews", - session_id=first.session_id, -) -follow_up_result = await client.runs.wait_for_completion(follow_up.id) -print(follow_up_result.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); - -const first = await client.runs.create({ - task: "Go to amazon.com, search for laptops, and open the first result", -}); -await client.runs.waitForCompletion(first.id); - -const followUp = await client.runs.create({ - task: "Extract the customer reviews", - sessionId: first.sessionId, -}); -const result = await client.runs.waitForCompletion(followUp.id); -console.log(result.result); -``` - - -The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. - -There is no separate empty-session creation step in V4: - -- Omit `session_id` / `sessionId` to create a new session implicitly. -- Pass a previous session ID to continue it explicitly. -- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. - -## Queue a follow-up - -Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. - - -```python Python -queued = await client.sessions.send_message( - first.session_id, - "Also compare the warranty options", -) -``` -```typescript TypeScript -const queued = await client.sessions.sendMessage(first.sessionId, { - text: "Also compare the warranty options", -}); -``` - - -Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](/cloud/api-v4/sessions/get-session) or [List runs](/cloud/api-v4/runs/list-runs) to discover the new run once it starts. - -See [Queue session message](/cloud/api-v4/sessions/queue-session-message) for the full request shape. diff --git a/docs/cloud/agent/human-in-the-loop.mdx b/docs/cloud/agent/human-in-the-loop.mdx index 7ef65d43..66cc9638 100644 --- a/docs/cloud/agent/human-in-the-loop.mdx +++ b/docs/cloud/agent/human-in-the-loop.mdx @@ -1,69 +1,43 @@ --- title: Human in the loop -description: "Open the V4 live browser, let a person take over, then continue the same session." +description: "Open the live browser, take over, then continue the same session." icon: hand --- -Use a human checkpoint for approvals, payments, complex authentication, or reviewing work before the agent continues. - -The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. +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.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Find noise-cancelling headphones on Amazon and stop before selecting a product" +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" ) -await client.runs.wait_for_completion(created.id) - -events = await client.runs.events(created.id, limit=100) -ready = next(event for event in events.events if event.type == "browser.ready") -live_url = ready.data["live_view_url"] -print(f"Open this live browser: {live_url}") +print(ready.data["live_view_url"]) -input("Press Enter after selecting a product...") - -follow_up = await client.runs.create( - "Get the selected product's name, price, and rating", - session_id=created.session_id, +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, ) -result = await client.runs.wait_for_completion(follow_up.id) -print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; -import * as readline from "node:readline/promises"; - -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +const events = await client.runs.events(run.id, { + limit: 100, }); -await client.runs.waitForCompletion(created.id); - -const events = await client.runs.events(created.id, { limit: 100 }); -const ready = events.events.find((event) => event.type === "browser.ready"); -const liveUrl = ready?.data.live_view_url; -console.log(`Open this live browser: ${liveUrl}`); - -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await rl.question("Press Enter after selecting a product..."); -rl.close(); - -const followUp = await client.runs.create({ - task: "Get the selected product's name, price, and rating", - sessionId: created.sessionId, +const ready = events.events.find( + (event) => event.type === "browser.ready", +); +console.log(ready?.data.live_view_url); + +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + sessionId: run.sessionId, }); -const result = await client.runs.waitForCompletion(followUp.id); -console.log(result.result); ``` -The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. - - - Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. - - -See [Get run events](/cloud/api-v4/runs/get-run-events) for the event response. +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 ca184884..0de266b8 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -4,57 +4,49 @@ description: "Choose a V4 model and understand its token pricing." icon: microchip --- -Pass `model` when you create a run. These are the models currently shown in the V4 agent UI: +Pass one of these API strings as `model` when creating a run: -| Model | API string | Input | Cache read | Output | Bring your own key | -| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | — | -Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. +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). - **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. + **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** + when accuracy matters most. ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Compare the top three project-management tools for a 20-person startup", +run = client.runs.create( + "Compare three project-management tools", model="claude-opus-5", ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Compare the top three project-management tools for a 20-person startup", +const run = await client.runs.create({ + task: "Compare three project-management tools", model: "claude-opus-5", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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": "Compare the top three project-management tools", "model": "claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"claude-opus-5"}' ``` ## Bring your own key -Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. - -With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. - -Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. +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 00000000..fdd84ba3 --- /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 or 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 995b641d..415fd6e7 100644 --- a/docs/cloud/agent/quickstart.mdx +++ b/docs/cloud/agent/quickstart.mdx @@ -1,46 +1,66 @@ --- -title: Introduction -description: "Run a long-horizon browser agent with one task and a few lines of code." +title: Run a task +description: "Give a high-accuracy browser agent a goal and get the result." icon: rocket --- -The SDK wraps the [API v4 Reference](/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. +Create 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 +``` ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -created = await client.runs.create("List the top 20 Hacker News posts and their points") -run = await client.runs.wait_for_completion(created.id) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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 Hacker News posts and their points"}' + -d '{"task":"Find the top Hacker News story"}' ``` -`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](/cloud/api-v4/runs/get-run-status), then fetches the full [run result](/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. +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** and a **workspace**: -Use the agent for: + + A run belongs to a conversation session and uses a persistent workspace + -- Data extraction and research across many pages -- Form filling, downloads, and multi-step workflows -- Authenticated work with browser profiles -- Long-running tasks that create or consume files -- Follow-up turns that preserve the same conversation, workspace, and live browser + + + Continue the same conversation and browser. + + + Keep files across runs and sessions. + + + Poll ordered events while a run is active. + + -See [Follow-up tasks](/cloud/agent/follow-up-tasks), [Live messages](/cloud/agent/streaming), and [Workspaces & files](/cloud/agent/workspaces) for the main V4 patterns. + + Give this compact context file to your coding agent. + diff --git a/docs/cloud/agent/sessions.mdx b/docs/cloud/agent/sessions.mdx new file mode 100644 index 00000000..79c85693 --- /dev/null +++ b/docs/cloud/agent/sessions.mdx @@ -0,0 +1,49 @@ +--- +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. +Every run creates one implicitly unless you pass an existing session ID. + + + A session containing a first run and a follow-up run + + +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", +}); +await client.runs.waitForCompletion(first.id); + +const followUp = await client.runs.create({ + task: "Now summarize the top story", + sessionId: first.sessionId, +}); +const result = await client.runs.waitForCompletion(followUp.id); +console.log(result.result); +``` + + +- Omit the session ID for a new conversation. +- Reuse it for a follow-up with the same context and workspace. +- Pass only a [workspace ID](/cloud/agent/workspaces) for a fresh conversation + that shares files. diff --git a/docs/cloud/agent/streaming.mdx b/docs/cloud/agent/streaming.mdx deleted file mode 100644 index 3a850b07..00000000 --- a/docs/cloud/agent/streaming.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Live messages -description: "Poll V4 run events incrementally to monitor progress or build a custom UI." -icon: message-lines ---- - -V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. - -Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. - - -```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse - -TERMINAL = {"completed", "failed", "cancelled"} - -client = AsyncBrowserUse() -created = await client.runs.create("Find the top story on Hacker News") - -after = None -while True: - page = await client.runs.events(created.id, after=after, limit=100) - for event in page.events: - print(event.type, event.data) - if page.next_after is not None: - after = page.next_after - - status = await client.runs.status(created.id) - if status.status.value in TERMINAL: - break - await asyncio.sleep(1) - -run = await client.runs.get(created.id) -print(run.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const TERMINAL = new Set(["completed", "failed", "cancelled"]); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find the top story on Hacker News", -}); - -let after: number | undefined; -while (true) { - const page = await client.runs.events(created.id, { after, limit: 100 }); - for (const event of page.events) { - console.log(event.type, event.data); - } - if (page.nextAfter != null) after = page.nextAfter; - - const { status } = await client.runs.status(created.id); - if (TERMINAL.has(status)) break; - await new Promise((resolve) => setTimeout(resolve, 1000)); -} - -const run = await client.runs.get(created.id); -console.log(run.result); -``` - - -The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. - -## Cancel a run - - -```python Python -cancelled = await client.runs.cancel(created.id) -print(cancelled.status) -``` -```typescript TypeScript -const cancelled = await client.runs.cancel(created.id); -console.log(cancelled.status); -``` - - -Cancelling a run does not delete its session. You can send another turn with the same session ID. - -## Related - -- [Get run events](/cloud/api-v4/runs/get-run-events) — event response and cursor fields -- [Get run status](/cloud/api-v4/runs/get-run-status) — lightweight poll target -- [Follow-up tasks](/cloud/agent/follow-up-tasks) — continue or queue work in the same session diff --git a/docs/cloud/agent/structured-output.mdx b/docs/cloud/agent/structured-output.mdx index ca69dcca..2a17894f 100644 --- a/docs/cloud/agent/structured-output.mdx +++ b/docs/cloud/agent/structured-output.mdx @@ -1,69 +1,41 @@ --- title: Structured output -description: "Ask for JSON, then validate the V4 run result in your application." +description: "Ask for JSON and validate the V4 result in your application." icon: table --- -V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - - - V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. - +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel -class Post(BaseModel): - name: str +class Story(BaseModel): + title: str points: int - comments: int - -class HNPosts(BaseModel): - posts: list[Post] -client = AsyncBrowserUse() -created = await client.runs.create( - """ - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - """ +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -run = await client.runs.wait_for_completion(created.id) -posts = HNPosts.model_validate_json(run.result or "{}") - -for post in posts.posts: - print(f"{post.name} ({post.points} pts)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const HNPosts = z.object({ - posts: z.array(z.object({ - name: z.string(), - points: z.number(), - comments: z.number(), - })), +const Story = z.object({ + title: z.string(), + points: z.number(), }); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: ` - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - `, +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', }); -const run = await client.runs.waitForCompletion(created.id); -const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); - -for (const post of posts.posts) { - console.log(`${post.name} (${post.points} pts)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` -For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. +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 ded3c5af..e2c71fbc 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -1,116 +1,76 @@ --- title: Workspaces & files -description: "Give a V4 run input files and retrieve files the agent creates." +description: "Persist files across V4 runs and conversations." icon: folder --- -Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. +A **workspace** is a persistent filesystem. A run can read attached inputs, +create files, and share those files with later sessions. -## Upload and attach input files + + Two independent sessions reading and writing the same workspace + -Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. +## Upload and attach a file ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="company-research") -uploaded = await client.workspaces.upload(workspace.id, "people.csv") - -created = await client.runs.create( - "Read the attached 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], ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "company-research" }); -const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); - -const created = await client.runs.create({ - task: "Read the attached people.csv and tell me who works at Google", - workspaceId: workspace.id, - attachedFileIds: [uploaded[0].id], +const workspace = await client.workspaces.create({ + name: "research", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); -``` - - -You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. - - -```python Python -uploaded = await client.workspaces.upload( - workspace.id, - "data.csv", - "config.json", - "image.png", -) -``` -```typescript TypeScript const uploaded = await client.workspaces.upload( workspace.id, - "data.csv", - "config.json", - "image.png", + "people.csv", ); + +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` - - Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. - +Attachments are turn-scoped. Reusing a workspace does not automatically attach +every upload to later runs. -## Retrieve files the agent creates +## Retrieve created files -Ask the agent to save its output in the workspace, then list files with temporary download URLs: +Ask the agent to save its output, then list the workspace: ```python Python -created = await client.runs.create( - "Save the top three Hacker News posts as outputs/posts.json", - workspace_id=workspace.id, -) -await client.runs.wait_for_completion(created.id) - -files = await client.workspaces.files( +files = client.workspaces.files( workspace.id, - prefix="outputs/", include_urls=True, ) for file in files.files: print(file.path, file.url) ``` ```typescript TypeScript -const created = await client.runs.create({ - task: "Save the top three Hacker News posts as outputs/posts.json", - workspaceId: workspace.id, -}); -await client.runs.waitForCompletion(created.id); - -const files = await client.workspaces.files(workspace.id, { - prefix: "outputs/", - includeUrls: true, -}); +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, +); for (const file of files.files) { console.log(file.path, file.url); } ``` -Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. - -## Reuse a workspace - -- Pass neither ID to `runs.create()` to create a new session and workspace. -- Pass `session_id` / `sessionId` to continue the same conversation and workspace. -- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. - -See [Upload workspace files](/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. +Download URLs expire after 60 seconds. See [Upload workspace +files](/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace +files](/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. diff --git a/docs/cloud/browser/live-preview.mdx b/docs/cloud/browser/live-preview.mdx index 5a67737f..e3797117 100644 --- a/docs/cloud/browser/live-preview.mdx +++ b/docs/cloud/browser/live-preview.mdx @@ -1,153 +1,86 @@ --- 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", }); -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", + browserSettings: { 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 85134290..a9718b35 100644 --- a/docs/cloud/browser/playwright-puppeteer-selenium.mdx +++ b/docs/cloud/browser/playwright-puppeteer-selenium.mdx @@ -1,12 +1,17 @@ --- 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. -## 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). + + +## WebSocket URL Connect with a single URL. All configuration is passed as query parameters. @@ -14,16 +19,16 @@ Connect with a single URL. All configuration is passed as query parameters. ```python Python -from playwright.async_api import async_playwright +from playwright.sync_api import sync_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) +with sync_playwright() as p: + browser = 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() + page.goto("https://example.com") + print(page.title()) + browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -56,24 +61,8 @@ 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. - +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. ## Query parameters @@ -86,72 +75,7 @@ with sync_playwright() as p: | `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. + Close the CDP connection when done. Browsers left running continue to incur + charges until their timeout expires. diff --git a/docs/cloud/browser/proxies.mdx b/docs/cloud/browser/proxies.mdx index 6ca1f747..ee3d58c0 100644 --- a/docs/cloud/browser/proxies.mdx +++ b/docs/cloud/browser/proxies.mdx @@ -1,84 +1,92 @@ --- 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. Set `browser_settings` / +`browserSettings` when you create a V4 run to choose another country: ```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", + 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", + 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", + } }, ) ``` ```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", + browserSettings: { + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + }, }, }); ``` + +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/stealth.mdx b/docs/cloud/browser/stealth.mdx index 288f9d08..0f64772d 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 945231d5..2aef8ab6 100644 --- a/docs/cloud/faq.mdx +++ b/docs/cloud/faq.mdx @@ -40,22 +40,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 vs v4 — which should I use? +## V2 or V4 — which should I use? -**Use v4 for new agent integrations.** It is designed for long-horizon work: +Use **V4** for difficult tasks where accuracy matters. It supports: - Run-focused API with a cheap status polling endpoint -- Conversation sessions with queued and interrupting follow-ups +- 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 -V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. +Use **V2** when tasks are simple and your priority is very low cost and +predictable speed. Its accuracy is substantially lower. -```python -# v4 (recommended for new agent runs) -from browser_use_sdk.v4 import AsyncBrowserUse - -# v3 (existing session-based integrations) -from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 -``` +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 5d3c02fc..3b1ab424 100644 --- a/docs/cloud/guides/2fa.mdx +++ b/docs/cloud/guides/2fa.mdx @@ -1,283 +1,63 @@ --- 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. - - -```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. +[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() -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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, +}); ``` -See [Human in the loop](/cloud/agent/human-in-the-loop) for more patterns. - ---- - -## 3. Agent Mail +This avoids another 2FA challenge while the site's cookies remain valid. -When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. +## Let a human take over -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, open its `live_view_url`, 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 +client.runs.wait_for_completion(first.id) -If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. - -When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. - - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# The TOTP secret from your authenticator setup — NOT the 6-digit code -totp_secret = "JBSWY3DPEHPK3PXP" - -result = await client.run( - f""" - Log into example.com with username user@example.com and password mypassword. - When prompted for a 2FA code, generate one using pyotp: - - import pyotp - totp = pyotp.TOTP("{totp_secret}") - code = totp.now() - - Enter the generated code. - """, +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(); +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", +}); +await client.runs.waitForCompletion(first.id); -// 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 nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + 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 7b5b776c..74bb64c7 100644 --- a/docs/cloud/guides/authentication.mdx +++ b/docs/cloud/guides/authentication.mdx @@ -1,98 +1,46 @@ --- 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. +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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, }); -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 b7edc630..1969b035 100644 --- a/docs/cloud/guides/profile-sync.mdx +++ b/docs/cloud/guides/profile-sync.mdx @@ -1,30 +1,38 @@ --- 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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, }); ``` + +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 9a38defb..6a2beaf1 100644 --- a/docs/cloud/guides/x402.mdx +++ b/docs/cloud/guides/x402.mdx @@ -3,7 +3,7 @@ title: x402 (pay-per-request) description: "Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request." --- - +{/* prettier-ignore-start */} [x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. @@ -371,4 +371,4 @@ const client = new BrowserUse({ x402 }); - [Standard API key auth](/cloud/quickstart) — alternative if you don't want pay-per-use - [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) - +{/* prettier-ignore-end */} diff --git a/docs/cloud/images/v4-agent-overview.excalidraw b/docs/cloud/images/v4-agent-overview.excalidraw new file mode 100644 index 00000000..034017a1 --- /dev/null +++ b/docs/cloud/images/v4-agent-overview.excalidraw @@ -0,0 +1,394 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 68, + "y": 38, + "width": 540, + "height": 38, + "text": "One task creates the context around it", + "originalText": "One task creates the context around it", + "fontSize": 30, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10101, + "version": 1, + "versionNonce": 20101, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "task", + "x": 70, + "y": 165, + "width": 225, + "height": 118, + "strokeColor": "#c2410c", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 100, + "y": 188, + "width": 165, + "height": 66, + "text": "YOUR TASK\nNatural-language goal", + "originalText": "YOUR TASK\nNatural-language goal", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 300, + "y": 224, + "width": 91, + "height": 0, + "strokeColor": "#c2410c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10104, + "version": 1, + "versionNonce": 20104, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [91, 0]], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "session", + "x": 398, + "y": 114, + "width": 326, + "height": 220, + "strokeColor": "#6d28d9", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 426, + "y": 134, + "width": 270, + "height": 30, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 22, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 438, + "y": 183, + "width": 246, + "height": 78, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 468, + "y": 197, + "width": 186, + "height": 49, + "text": "RUN\nExecutes one turn", + "originalText": "RUN\nExecutes one turn", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": "text", + "id": "sessionDetail", + "x": 454, + "y": 284, + "width": 214, + "height": 26, + "text": "conversation + live browser", + "originalText": "conversation + live browser", + "fontSize": 15, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10109, + "version": 1, + "versionNonce": 20109, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "runToWorkspace", + "x": 730, + "y": 224, + "width": 89, + "height": 0, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 826, + "y": 137, + "width": 292, + "height": 174, + "strokeColor": "#047857", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 858, + "y": 171, + "width": 228, + "height": 100, + "text": "WORKSPACE\nPersistent files\ninputs • scripts • outputs", + "originalText": "WORKSPACE\nPersistent files\ninputs • scripts • outputs", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10112, + "version": 1, + "versionNonce": 20112, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "text", + "id": "footer", + "x": 272, + "y": 382, + "width": 650, + "height": 29, + "text": "Omit both IDs and API V4 creates the session and workspace automatically.", + "originalText": "Omit both IDs and API V4 creates the session and workspace automatically.", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10113, + "version": 1, + "versionNonce": 20113, + "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-agent-overview.png b/docs/cloud/images/v4-agent-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..a46f347520742a25ea6bb64b2beeb1c482c17556 GIT binary patch literal 114314 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

WnL7E#iNrmPNf-a&nItA7SK6P?HD;UUUP17 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 g)*k==U9qNH0&SV^XZx<#(N{Tr%EO6x; + + + + + + + + + + + One task creates the context around it + + + YOUR TASK + Natural-language goal + + + + SESSION + + RUN + Executes one turn + conversation + live browser + + + + WORKSPACE + Persistent files + inputs • scripts • outputs + + Omit both IDs and API V4 creates the session and workspace automatically. + diff --git a/docs/cloud/images/v4-sessions.excalidraw b/docs/cloud/images/v4-sessions.excalidraw new file mode 100644 index 00000000..8a5426de --- /dev/null +++ b/docs/cloud/images/v4-sessions.excalidraw @@ -0,0 +1,362 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 65, + "y": 38, + "width": 410, + "height": 38, + "text": "A session is a conversation", + "originalText": "A session is a conversation", + "fontSize": 30, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10201, + "version": 1, + "versionNonce": 20201, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "session", + "x": 66, + "y": 112, + "width": 1048, + "height": 250, + "strokeColor": "#6d28d9", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 98, + "y": 132, + "width": 205, + "height": 29, + "text": "ONE SESSION ID", + "originalText": "ONE SESSION ID", + "fontSize": 21, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 106, + "y": 194, + "width": 245, + "height": 90, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 128, + "y": 211, + "width": 201, + "height": 54, + "text": "RUN 1\n“Open Hacker News”", + "originalText": "RUN 1\n“Open Hacker News”", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 357, + "y": 239, + "width": 87, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10206, + "version": 1, + "versionNonce": 20206, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [87, 0]], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run2", + "x": 450, + "y": 194, + "width": 280, + "height": 90, + "strokeColor": "#1e3a5f", + "backgroundColor": "#60a5fa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 472, + "y": 211, + "width": 236, + "height": 54, + "text": "RUN 2\n“Summarize the top story”", + "originalText": "RUN 2\n“Summarize the top story”", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 736, + "y": 239, + "width": 87, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10209, + "version": 1, + "versionNonce": 20209, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [87, 0]], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run3", + "x": 829, + "y": 194, + "width": 245, + "height": 90, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 851, + "y": 211, + "width": 201, + "height": 54, + "text": "RUN 3\nAnother follow-up", + "originalText": "RUN 3\nAnother follow-up", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10211, + "version": 1, + "versionNonce": 20211, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.35 + }, + { + "type": "text", + "id": "footer", + "x": 236, + "y": 395, + "width": 708, + "height": 29, + "text": "Conversation, workspace, and the live browser are carried into each follow-up.", + "originalText": "Conversation, workspace, and the live browser are carried into each follow-up.", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10212, + "version": 1, + "versionNonce": 20212, + "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-sessions.png b/docs/cloud/images/v4-sessions.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0a728d2110819f82289324bce825627aa70be6 GIT binary patch literal 85025 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 DAw + + + + + + + + A session is a conversation + + ONE SESSION ID + + + RUN 1 + “Open Hacker News” + + + + RUN 2 + “Summarize the top story” + + + + RUN 3 + Another follow-up + + Conversation, workspace, and the live browser carry into each follow-up. + diff --git a/docs/cloud/images/v4-workspaces.excalidraw b/docs/cloud/images/v4-workspaces.excalidraw new file mode 100644 index 00000000..0afe20bf --- /dev/null +++ b/docs/cloud/images/v4-workspaces.excalidraw @@ -0,0 +1,476 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 65, + "y": 38, + "width": 505, + "height": 38, + "text": "A workspace persists beyond a session", + "originalText": "A workspace persists beyond a session", + "fontSize": 30, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10301, + "version": 1, + "versionNonce": 20301, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "session1", + "x": 74, + "y": 132, + "width": 270, + "height": 112, + "strokeColor": "#6d28d9", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 108, + "y": 155, + "width": 202, + "height": 62, + "text": "SESSION A\nUpload + create files", + "originalText": "SESSION A\nUpload + create files", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 74, + "y": 300, + "width": 270, + "height": 112, + "strokeColor": "#1e40af", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "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": 108, + "y": 323, + "width": 202, + "height": 62, + "text": "SESSION B\nFresh session, same files", + "originalText": "SESSION B\nFresh session, same files", + "fontSize": 17, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 350, + "y": 188, + "width": 165, + "height": 78, + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10306, + "version": 1, + "versionNonce": 20306, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [165, 78]], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow2", + "x": 350, + "y": 356, + "width": 165, + "height": -78, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10307, + "version": 1, + "versionNonce": 20307, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [165, -78]], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 522, + "y": 122, + "width": 584, + "height": 300, + "strokeColor": "#047857", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 566, + "y": 145, + "width": 496, + "height": 30, + "text": "PERSISTENT WORKSPACE", + "originalText": "PERSISTENT WORKSPACE", + "fontSize": 22, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "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": 577, + "y": 211, + "width": 142, + "height": 95, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "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": 596, + "y": 240, + "width": 104, + "height": 39, + "text": "people.csv", + "originalText": "people.csv", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10311, + "version": 1, + "versionNonce": 20311, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "file2", + "x": 743, + "y": 211, + "width": 142, + "height": 95, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10312, + "version": 1, + "versionNonce": 20312, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": {"type": 3} + }, + { + "type": "text", + "id": "file2Text", + "x": 762, + "y": 240, + "width": 104, + "height": 39, + "text": "script.py", + "originalText": "script.py", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10313, + "version": 1, + "versionNonce": 20313, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "file3", + "x": 909, + "y": 211, + "width": 142, + "height": 95, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10314, + "version": 1, + "versionNonce": 20314, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": {"type": 3} + }, + { + "type": "text", + "id": "file3Text", + "x": 928, + "y": 240, + "width": 104, + "height": 39, + "text": "output.json", + "originalText": "output.json", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10315, + "version": 1, + "versionNonce": 20315, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "text", + "id": "workspaceDetail", + "x": 628, + "y": 347, + "width": 372, + "height": 29, + "text": "Reuse with workspace_id / workspaceId", + "originalText": "Reuse with workspace_id / workspaceId", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 10316, + "version": 1, + "versionNonce": 20316, + "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.png b/docs/cloud/images/v4-workspaces.png new file mode 100644 index 0000000000000000000000000000000000000000..47fbb08cebe9c95a2b4da69d6252125e4394b486 GIT binary patch literal 113738 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 HXKwr(_df6a literal 0 HcmV?d00001 diff --git a/docs/cloud/images/v4-workspaces.svg b/docs/cloud/images/v4-workspaces.svg new file mode 100644 index 00000000..07f63d2c --- /dev/null +++ b/docs/cloud/images/v4-workspaces.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + A workspace persists beyond a session + + + SESSION A + Upload + create files + + + SESSION B + Fresh session, same files + + + + + + PERSISTENT WORKSPACE + + + people.csv + + script.py + + output.json + + Reuse with workspace_id / workspaceId + diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 4e59cff7..13b4251e 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -5,8 +5,16 @@ Source: https://docs.browser-use.com/cloud/quickstart +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 +``` + ## 1. Install +Skip this step if you use curl. + ```bash Python pip install browser-use-sdk ``` @@ -14,60 +22,35 @@ 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 +## 2. Run a task ```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse - -async def main(): - client = AsyncBrowserUse() - created = await client.runs.create("List the top 20 Hacker News posts and their points") - run = await client.runs.wait_for_completion(created.id) - print(run.result) +from browser_use_sdk.v4 import BrowserUse -asyncio.run(main()) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +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"}' ``` -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** | `runs.create()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | `browserSettings` | ✓ | -| custom proxy | `browserSettings` | ✓ | -| profile | `browserSettings` | ✓ | -| recording | `browserSettings` | ✓ | -| workspace & files | ✓ | — | -| follow-up conversation | ✓ | — | -| screen size | `browserSettings` | ✓ | -| 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). - +Sessions, workspaces, models, and observability. +A compact, API V4-first context file for coding agents. # Prompt for Vibecoders Source: https://docs.browser-use.com/cloud/vibecoding @@ -79,566 +62,369 @@ Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsur https://docs.browser-use.com/cloud/llms.txt ``` - -# Introduction +# Run a task Source: https://docs.browser-use.com/cloud/agent/quickstart -The SDK wraps the [API v4 Reference](https://docs.browser-use.com/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. +Create 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 +``` ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -created = await client.runs.create("List the top 20 Hacker News posts and their points") -run = await client.runs.wait_for_completion(created.id) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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 Hacker News posts and their points"}' + -d '{"task":"Find the top Hacker News story"}' ``` -`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status), then fetches the full [run result](https://docs.browser-use.com/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. +Install the SDK with `pip install browser-use-sdk` or +`npm install browser-use-sdk`. Curl needs no installation. -Use the agent for: +Every new run implicitly creates a **session** and a **workspace**: -- Data extraction and research across many pages -- Form filling, downloads, and multi-step workflows -- Authenticated work with browser profiles -- Long-running tasks that create or consume files -- Follow-up turns that preserve the same conversation, workspace, and live browser + + + -See [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks), [Live messages](https://docs.browser-use.com/cloud/agent/streaming), and [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces) for the main V4 patterns. +Continue the same conversation and browser. +Keep files across runs and sessions. +Poll ordered events while a run is active. + Give this compact context file to your coding agent. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` when you create a run. These are the models currently shown in the V4 agent UI: +Pass one of these API strings as `model` when creating a run: -| Model | API string | Input | Cache read | Output | Bring your own key | -| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | — | -Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. +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). - **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. + **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** + when accuracy matters most. ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Compare the top three project-management tools for a 20-person startup", +run = client.runs.create( + "Compare three project-management tools", model="claude-opus-5", ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Compare the top three project-management tools for a 20-person startup", +const run = await client.runs.create({ + task: "Compare three project-management tools", model: "claude-opus-5", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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": "Compare the top three project-management tools", "model": "claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"claude-opus-5"}' ``` ## Bring your own key -Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. - -With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. - -Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. - +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 -V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - - V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel -class Post(BaseModel): - name: str +class Story(BaseModel): + title: str points: int - comments: int - -class HNPosts(BaseModel): - posts: list[Post] -client = AsyncBrowserUse() -created = await client.runs.create( - """ - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - """ +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -run = await client.runs.wait_for_completion(created.id) -posts = HNPosts.model_validate_json(run.result or "{}") - -for post in posts.posts: - print(f"{post.name} ({post.points} pts)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const HNPosts = z.object({ - posts: z.array(z.object({ - name: z.string(), - points: z.number(), - comments: z.number(), - })), +const Story = z.object({ + title: z.string(), + points: z.number(), }); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: ` - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - `, +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', }); -const run = await client.runs.waitForCompletion(created.id); -const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); - -for (const post of posts.posts) { - console.log(`${post.name} (${post.points} pts)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` -For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. +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. +# Sessions +Source: https://docs.browser-use.com/cloud/agent/sessions -# Follow-up tasks -Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks +A **session** holds the agent's conversation and can reuse its live browser. +Every run creates one implicitly unless you pass an existing session ID. -Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: + + + -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() +Pass `session_id` / `sessionId` to continue: -first = await client.runs.create( - "Go to amazon.com, search for laptops, and open the first result" -) -first_result = await client.runs.wait_for_completion(first.id) +```python Python +first = client.runs.create("Open Hacker News") +client.runs.wait_for_completion(first.id) -follow_up = await client.runs.create( - "Extract the customer reviews", +follow_up = client.runs.create( + "Now summarize the top story", session_id=first.session_id, ) -follow_up_result = await client.runs.wait_for_completion(follow_up.id) -print(follow_up_result.result) +result = client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); - const first = await client.runs.create({ - task: "Go to amazon.com, search for laptops, and open the first result", + task: "Open Hacker News", }); await client.runs.waitForCompletion(first.id); const followUp = await client.runs.create({ - task: "Extract the customer reviews", + task: "Now summarize the top story", sessionId: first.sessionId, }); const result = await client.runs.waitForCompletion(followUp.id); console.log(result.result); ``` -The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. - -There is no separate empty-session creation step in V4: - -- Omit `session_id` / `sessionId` to create a new session implicitly. -- Pass a previous session ID to continue it explicitly. -- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. - -## Queue a follow-up - -Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. - -```python Python -queued = await client.sessions.send_message( - first.session_id, - "Also compare the warranty options", -) -``` -```typescript TypeScript -const queued = await client.sessions.sendMessage(first.sessionId, { - text: "Also compare the warranty options", -}); -``` - -Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](https://docs.browser-use.com/cloud/api-v4/sessions/get-session) or [List runs](https://docs.browser-use.com/cloud/api-v4/runs/list-runs) to discover the new run once it starts. - -See [Queue session message](https://docs.browser-use.com/cloud/api-v4/sessions/queue-session-message) for the full request shape. - - -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming - - -V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. - -Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. - -```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse - -TERMINAL = {"completed", "failed", "cancelled"} - -client = AsyncBrowserUse() -created = await client.runs.create("Find the top story on Hacker News") - -after = None -while True: - page = await client.runs.events(created.id, after=after, limit=100) - for event in page.events: - print(event.type, event.data) - if page.next_after is not None: - after = page.next_after - - status = await client.runs.status(created.id) - if status.status.value in TERMINAL: - break - await asyncio.sleep(1) - -run = await client.runs.get(created.id) -print(run.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const TERMINAL = new Set(["completed", "failed", "cancelled"]); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find the top story on Hacker News", -}); - -let after: number | undefined; -while (true) { - const page = await client.runs.events(created.id, { after, limit: 100 }); - for (const event of page.events) { - console.log(event.type, event.data); - } - if (page.nextAfter != null) after = page.nextAfter; - - const { status } = await client.runs.status(created.id); - if (TERMINAL.has(status)) break; - await new Promise((resolve) => setTimeout(resolve, 1000)); -} - -const run = await client.runs.get(created.id); -console.log(run.result); -``` - -The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. - -## Cancel a run - -```python Python -cancelled = await client.runs.cancel(created.id) -print(cancelled.status) -``` -```typescript TypeScript -const cancelled = await client.runs.cancel(created.id); -console.log(cancelled.status); -``` - -Cancelling a run does not delete its session. You can send another turn with the same session ID. - -## Related - -- [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) — event response and cursor fields -- [Get run status](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status) — lightweight poll target -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue or queue work in the same session - +- Omit the session ID for a new conversation. +- Reuse it for a follow-up with the same context and workspace. +- Pass only a [workspace ID](https://docs.browser-use.com/cloud/agent/workspaces) for a fresh conversation + that shares files. # Workspaces & files Source: https://docs.browser-use.com/cloud/agent/workspaces -Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. +A **workspace** is a persistent filesystem. A run can read attached inputs, +create files, and share those files with later sessions. -## Upload and attach input files + + + -Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. +## Upload and attach a file ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="company-research") -uploaded = await client.workspaces.upload(workspace.id, "people.csv") +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -created = await client.runs.create( - "Read the attached 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], ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "company-research" }); -const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); - -const created = await client.runs.create({ - task: "Read the attached people.csv and tell me who works at Google", - workspaceId: workspace.id, - attachedFileIds: [uploaded[0].id], +const workspace = await client.workspaces.create({ + name: "research", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); -``` - -You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. - -```python Python -uploaded = await client.workspaces.upload( - workspace.id, - "data.csv", - "config.json", - "image.png", -) -``` -```typescript TypeScript const uploaded = await client.workspaces.upload( workspace.id, - "data.csv", - "config.json", - "image.png", + "people.csv", ); + +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` - Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. +Attachments are turn-scoped. Reusing a workspace does not automatically attach +every upload to later runs. -## Retrieve files the agent creates +## Retrieve created files -Ask the agent to save its output in the workspace, then list files with temporary download URLs: +Ask the agent to save its output, then list the workspace: ```python Python -created = await client.runs.create( - "Save the top three Hacker News posts as outputs/posts.json", - workspace_id=workspace.id, -) -await client.runs.wait_for_completion(created.id) - -files = await client.workspaces.files( +files = client.workspaces.files( workspace.id, - prefix="outputs/", include_urls=True, ) for file in files.files: print(file.path, file.url) ``` ```typescript TypeScript -const created = await client.runs.create({ - task: "Save the top three Hacker News posts as outputs/posts.json", - workspaceId: workspace.id, -}); -await client.runs.waitForCompletion(created.id); - -const files = await client.workspaces.files(workspace.id, { - prefix: "outputs/", - includeUrls: true, -}); +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, +); for (const file of files.files) { console.log(file.path, file.url); } ``` -Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. - -## Reuse a workspace - -- Pass neither ID to `runs.create()` to create a new session and workspace. -- Pass `session_id` / `sessionId` to continue the same conversation and workspace. -- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. - -See [Upload workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. - +Download URLs expire after 60 seconds. See [Upload workspace +files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace +files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. # Deterministic rerun Source: https://docs.browser-use.com/cloud/agent/cache-script -For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. - -You can create the workspace in the dashboard or through the API: +Create one [workspace](https://docs.browser-use.com/cloud/agent/workspaces) for the workflow, then use +these prompts with the same `workspace_id` / `workspaceId`. The [run +code](https://docs.browser-use.com/cloud/agent/quickstart) stays exactly the same. -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +## First run -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="hn-scraper") - -created = await client.runs.create( - """ - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - """, - workspace_id=workspace.id, -) -first = await client.runs.wait_for_completion(created.id) -print(first.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; +```text +Complete this task: get the top five Hacker News stories as JSON. -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "hn-scraper" }); - -const created = await client.runs.create({ - task: ` - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - `, - workspaceId: workspace.id, -}); -const first = await client.runs.waitForCompletion(created.id); -console.log(first.result); +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, start a new run in the same workspace and tell the agent to use the saved script: +## Later runs -```python Python -created = await client.runs.create( - "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspace_id=workspace.id, -) -rerun = await client.runs.wait_for_completion(created.id) -print(rerun.result) -``` -```typescript TypeScript -const created = await client.runs.create({ - task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspaceId: workspace.id, -}); -const rerun = await client.runs.waitForCompletion(created.id); -console.log(rerun.result); +```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 pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. - - V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. - +This still starts an agent and uses tokens. The saved script gives the agent a +faster, more predictable path; it is not automatic zero-LLM execution. # Human in the loop Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -Use a human checkpoint for approvals, payments, complex authentication, or reviewing work before the agent continues. - -The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. +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.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Find noise-cancelling headphones on Amazon and stop before selecting a product" +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" ) -await client.runs.wait_for_completion(created.id) - -events = await client.runs.events(created.id, limit=100) -ready = next(event for event in events.events if event.type == "browser.ready") -live_url = ready.data["live_view_url"] -print(f"Open this live browser: {live_url}") - -input("Press Enter after selecting a product...") +print(ready.data["live_view_url"]) -follow_up = await client.runs.create( - "Get the selected product's name, price, and rating", - session_id=created.session_id, +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, ) -result = await client.runs.wait_for_completion(follow_up.id) -print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; -import * as readline from "node:readline/promises"; +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); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + sessionId: run.sessionId, }); -await client.runs.waitForCompletion(created.id); +``` -const events = await client.runs.events(created.id, { limit: 100 }); -const ready = events.events.find((event) => event.type === "browser.ready"); -const liveUrl = ready?.data.live_view_url; -console.log(`Open this live browser: ${liveUrl}`); +The same session preserves the conversation and workspace and reuses the live +browser while it is available. Treat live-view URLs as credentials. -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await rl.question("Press Enter after selecting a product..."); -rl.close(); +# Observability +Source: https://docs.browser-use.com/cloud/agent/observability -const followUp = await client.runs.create({ - task: "Get the selected product's name, price, and rating", - sessionId: created.sessionId, -}); -const result = await client.runs.waitForCompletion(followUp.id); -console.log(result.result); -``` -The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. +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 or after - Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. + 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; -See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) for the event response. + 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](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) +for the complete response shape. -# Introduction Stealth +# Stealth Source: https://docs.browser-use.com/cloud/browser/stealth @@ -656,223 +442,171 @@ Every cloud browser session runs in a hardened Chromium fork with stealth enable 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. - # Proxies Source: https://docs.browser-use.com/cloud/browser/proxies -A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. +A US residential proxy is enabled by default. Set `browser_settings` / +`browserSettings` when you create a V4 run to choose another country: ```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( + "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", + 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", + 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", + } }, ) ``` ```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", + browserSettings: { + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + }, }, }); ``` +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. # Live preview & recording Source: https://docs.browser-use.com/cloud/browser/live-preview - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). - -`liveUrl` is returned on session creation. +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 = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) + +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", }); -console.log(session.liveUrl); -``` +await client.runs.waitForCompletion(run.id); -`liveUrl` is also returned when creating a standalone browser session: - -```python Python -browser = await client.browsers.create() -print(browser.live_url) -``` -```typescript TypeScript -const browser = await client.browsers.create(); -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 +Poll [run events](https://docs.browser-use.com/cloud/agent/observability) if you need the URL as soon as +the browser starts. -Useful for human interaction or to see live what's happening. +## Embed the live browser ```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: +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. -``` -Content-Security-Policy: frame-src 'self' https://live.browser-use.com; -``` +## Recording -For responsive sizing, use CSS instead of fixed dimensions: - -```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 -``` - -## 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", + browserSettings: { 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](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 - - +The MP4 becomes available in the Dashboard after the browser stops. API runs +default to recording off, and Zero Data Retention projects never record. # Playwright, Puppeteer, Selenium Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium @@ -880,23 +614,26 @@ 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) + 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). + +## WebSocket URL Connect with a single URL. All configuration is passed as query parameters. ### Playwright ```python Python -from playwright.async_api import async_playwright +from playwright.sync_api import sync_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) +with sync_playwright() as p: + browser = 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() + page.goto("https://example.com") + print(page.title()) + browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -928,22 +665,8 @@ 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. +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. ## Query parameters @@ -956,453 +679,144 @@ with sync_playwright() as p: | `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. - + Close the CDP connection when done. Browsers left running continue to incur + charges until their timeout expires. # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +A profile persists cookies, local storage, and login state across browsers. +Create or select one under [Dashboard → Profiles](https://cloud.browser-use.com/settings?tab=profiles), +then pass its ID in V4 browser settings: -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) +```python Python +from browser_use_sdk.v4 import BrowserUse -# Always stop the session to persist profile state -await client.sessions.stop(session.id) +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", + browserSettings: { profileId: "YOUR_PROFILE_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) +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` -```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. - - 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. +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. +For the fastest setup, [sync an existing local login](https://docs.browser-use.com/cloud/guides/profile-sync). # Sync local and cloud cookies Source: https://docs.browser-use.com/cloud/guides/profile-sync +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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, }); ``` +The profile supplies cookies and local storage without putting credentials in +the prompt. Re-sync when the site's login expires. # 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. +The most reliable options are a saved profile or a human checkpoint. -```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 }); +## Reuse a logged-in profile -// 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. +[Sync your local login](https://docs.browser-use.com/cloud/guides/profile-sync), then load that profile in +the run: ```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, +run = client.runs.create( + "Download my latest invoice", + browser_settings={"profileId": "YOUR_PROFILE_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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, +}); ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. - ---- - -## 3. Agent Mail +This avoids another 2FA challenge while the site's cookies remain valid. -When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. +## Let a human take over -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, open its `live_view_url`, 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 +client.runs.wait_for_completion(first.id) -If you're automating on behalf of your users and they need to receive 2FA codes: - -1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). -2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. - -This way, your client's real email stays private — the agent only sees the forwarded verification emails. - -### Connect external email via Composio - -You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. - ---- - -## 4. TOTP secret in prompt - -If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. - -When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# The TOTP secret from your authenticator setup — NOT the 6-digit code -totp_secret = "JBSWY3DPEHPK3PXP" - -result = await client.run( - f""" - Log into example.com with username user@example.com and password mypassword. - When prompted for a 2FA code, generate one using pyotp: - - import pyotp - totp = pyotp.TOTP("{totp_secret}") - code = totp.now() - - Enter the generated code. - """, +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() +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", +}); +await client.runs.waitForCompletion(first.id); - Enter the generated code.`, -); -console.log(result.output); +const nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + 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](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. # Claude Code Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code @@ -1474,30 +888,6 @@ browser-use auth status 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. -## 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](https://docs.browser-use.com/cloud/guides/x402). - - # Claude Managed Agents Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents @@ -1584,7 +974,6 @@ The agent starts a named cloud browser, runs Python helper snippets through `bro 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 @@ -1623,1007 +1012,193 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: }, }, }, -} -``` - -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 -uv tool install browser-use -``` - -**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 drive pages through Browser Harness and Python helpers. - -For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). - - -# Hermes Agent -Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent - - -[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. - -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. - -## 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. - -### Setup - -**1. Get your API key** - -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). - -Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - -**2. Configure Hermes** - -Run the setup wizard: - -```bash -hermes setup tools -``` - -Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. - -Or configure manually — add your key to `~/.hermes/.env`: - -```bash -BROWSER_USE_API_KEY=your_key_here -``` - -And set the provider in `~/.hermes/config.yaml`: - -```yaml -browser: - cloud_provider: browser-use -``` - -**3. Use it** - -Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: - -``` -> Find the top trending repositories on GitHub today and summarize them -``` - -## Option 2: Browser Use CLI - -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. - -### Setup - -**1. Install the CLI** - -```bash -uv tool install browser-use -``` - -**2. Verify the installation** - -```bash -browser-use doctor -``` - -**3. Register the skill** - -Register the Browser Use skill with the installed CLI: - -```bash -browser-use skill install -``` - -Or ask Hermes directly in chat to install it. - -**4. Authenticate for cloud browsers** - -Authenticate with your API key: - -```bash -browser-use auth login -``` - -Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - -**5. Use it** - -Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: - -``` -> Use browser-use to open github.com/trending and summarize the top repos -``` - -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). - -## Agent Self-Registration - -Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. - -Install the Browser Use CLI and skill: - -```bash -uv tool install browser-use -browser-use skill install -``` - -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** - -For the cloud browser backend (Option 1): - -```bash -hermes config set BROWSER_USE_API_KEY -``` - -For CLI mode (Option 2), put the key in the agent's shell environment: - -```bash -export BROWSER_USE_API_KEY=bu_... -browser-use auth status -``` - -### 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. - - -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server - - -``` -https://api.browser-use.com/v3/mcp -``` - -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). - -## Claude Code - -```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp -``` - -## Claude Desktop - -Add to `claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Cursor - -Add to `.cursor/mcp.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Windsurf - -Add to `~/.codeium/windsurf/mcp_config.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "serverUrl": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Available Tools - -| Tool | Description | -|------|-------------| -| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | -| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | -| `send_task` | Send a follow-up task to an idle keep-alive session. | -| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | -| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | -| `list_sessions` | List recent sessions with status and cost. | -| `list_browser_profiles` | List browser profiles for authenticated tasks. | - - -# Webhooks -Source: https://docs.browser-use.com/cloud/guides/webhooks - - -Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). - -## Events - -| Event | When | -|-------|------| -| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | -| `test` | Webhook test ping | - -## Payload - -```json -{ - "type": "agent.task.status_update", - "timestamp": "2025-01-15T10:30:00Z", - "payload": { - "task_id": "task_abc123", - "session_id": "session_xyz", - "status": "idle", - "metadata": {} - } -} -``` - -## Signature verification - -Every webhook request includes two headers: - -- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload -- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent - -The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. - -```python Python -import hashlib -import hmac -import json -import time - -def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: - # Reject requests older than 5 minutes - try: - ts = int(timestamp) - except (ValueError, TypeError): - return False - if abs(time.time() - ts) > 300: - return False - payload = json.loads(body) - message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" - expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() - return hmac.compare_digest(expected, signature) -``` -```typescript TypeScript -import { createHmac, timingSafeEqual } from "crypto"; - -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { - return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} - -function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { - // Reject requests older than 5 minutes - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", secret).update(message).digest("hex"); - return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); -} -``` - -## Example: Express webhook handler - -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; - -const app = express(); -app.use(express.raw({ type: "application/json" })); - -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; - -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { - return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} - -app.post("/webhook", (req, res) => { - const signature = req.headers["x-browser-use-signature"] as string; - const timestamp = req.headers["x-browser-use-timestamp"] as string; - - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { - return res.status(401).send("Request too old"); - } - - const body = req.body.toString(); - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); - - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { - return res.status(401).send("Invalid signature"); - } - - if (payload.type === "agent.task.status_update") { - const { task_id, status, session_id } = payload.payload; - console.log(`Task ${task_id} is now ${status}`); - } - - res.status(200).send("OK"); -}); - -app.listen(3000); -``` - -## Example: FastAPI webhook handler - -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time - -app = FastAPI() - -WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] - -@app.post("/webhook") -async def handle_webhook(request: Request): - body = await request.body() - signature = request.headers.get("x-browser-use-signature", "") - timestamp = request.headers.get("x-browser-use-timestamp", "") - - # Reject requests older than 5 minutes - try: - ts = int(timestamp) - except (ValueError, TypeError): - raise HTTPException(status_code=401, detail="Invalid timestamp") - if abs(time.time() - ts) > 300: - raise HTTPException(status_code=401, detail="Request too old") - - payload = json.loads(body) - message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" - expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() - - if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") - - if payload["type"] == "agent.task.status_update": - task_id = payload["payload"]["task_id"] - status = payload["payload"]["status"] - print(f"Task {task_id} is now {status}") - - return {"status": "ok"} -``` - - For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. - - -# x402 (pay-per-request) -Source: https://docs.browser-use.com/cloud/guides/x402 - - - - -[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. - -x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. - - -**New to crypto?** Here's the gist: - -- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. -- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. -- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. -- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. - - -**Three ways to start, ranked by laziness:** - -One command. Claude does the wallet setup, funding walkthrough, and -verification for you. -One line in your Python or TypeScript app. Bring your own wallet. -Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. - -## Claude Code quickstart - -The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: - -```bash -npx skills add https://github.com/browser-use/browser-use --skill x402 -``` - -Then in Claude Code: - -``` -> /x402 -``` - -Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. - - Already have a Browser Use Cloud account? The skill detects this and switches - to **top-up mode**, adding credits to that existing account instead of - creating a new, wallet-keyed one. - -## SDK quickstart - -The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. - -```bash Python -pip install "browser-use-sdk[x402]" -``` -```bash TypeScript -npm install browser-use-sdk @x402/fetch @x402/evm viem -``` - -```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse - -async def main(): - client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base - result = await client.run("Go to example.com and tell me the heading.") - print(result.output) - -asyncio.run(main()) -``` - -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base -const result = await client.run("Go to example.com and tell me the heading."); -console.log(result.output); -``` - -Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: - -```python Python -client = AsyncBrowserUse() # auto-detects from env -``` -```typescript TypeScript -const client = new BrowserUse(); // auto-detects from env -``` - - Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. - -## Raw HTTP quickstart - -Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): - -```python -import asyncio - -from x402 import x402Client -from x402.http.clients import x402HttpxClient -from x402.mechanisms.evm import EthAccountSigner -from x402.mechanisms.evm.exact.register import register_exact_evm_client -from eth_account import Account - -async def main(): - client = x402Client() - register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) - - async with x402HttpxClient(client, timeout=120.0) as http: - response = await http.post( - "https://x402.api.browser-use.com/api/v3/sessions", - json={"task": "..."}, - ) - print(response.status_code, response.text[:500]) - -asyncio.run(main()) -``` - -`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. - -## What you need - -- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app -- **USD Coin (USDC) on Base mainnet** -- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) - -You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. - - -## Pricing and credits - -Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** - - **Mid-task drain still terminates the task.** Browser Use sessions run on a - worker that doesn't see x402, so once a long-running task starts and burns - through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause - and wait for the next x402 payment. The `$5` default exists so most tasks - complete without hitting this; for expensive models (e.g. Opus) or long - sessions, pre-fund with multiple requests before kicking off the task. - -See the [pricing page](https://browser-use.com/pricing) for model and browser costs. - -## Topping up an existing account - -If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: - -```python Python -import asyncio - -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse( - api_key="bu_...", # existing API key getting topped up - x402_private_key="0x...", # wallet that pays - base_url="https://x402.api.browser-use.com/api/v3", -) -async def main(): - result = await client.run("...") # $5 USDC charged, credited to the API key's project - print(result.output) - -asyncio.run(main()) - -``` - -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse({ - apiKey: "bu_...", - x402PrivateKey: "0x...", - baseUrl: "https://x402.api.browser-use.com/api/v3", -}); -const result = await client.run("..."); -``` - -When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: - -- Agents that ran out of free-tier credits and need to keep going -- Adding credits via crypto when you already have a regular Browser Use account -- Multi-wallet setups funding one shared account - -## Checking your credit balance - -When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. - -This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. - -To check how much credit that account has left, use the method below: - -```python Python -import asyncio - -from browser_use_sdk.v3 import get_wallet_balance - -async def main(): - balance = await get_wallet_balance("0x...") # same wallet private key you pay with - print(balance["total_credits_usd"]) - -asyncio.run(main()) - -``` - -```typescript TypeScript -import { getWalletBalance } from "browser-use-sdk/v3"; - -const balance = await getWalletBalance("0x..."); // same wallet private key you pay with -console.log(balance.total_credits_usd); -``` - -The response contains: - -| Field | Description | -| ------------------------ | ------------------------------------------------------------------------------- | -| `wallet` | The wallet address (lowercased) | -| `project_id` | The account (project) tied to your wallet that the credits live in | -| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | -| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | - - This is for accounts created from a wallet (the default x402 mode). If you're - [topping up an existing account](#topping-up-an-existing-account), check that - account's balance the normal way with your API key via - `client.billing.account()`. A wallet that has never paid yet has no account, - so the call returns `404` until the first payment. - - The SDK signs a fixed, server-defined message - ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with - Ethereum" mechanism) with your wallet's private key. The signature proves you - control the address without moving any funds. The server recovers the signer, - matches it to the wallet's project, and returns the balance. - -## How it works - -Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. - -A bit more detail: - -1. Your code makes a request (e.g. "run this task"). -2. The SDK auto-signs the payment from your wallet and resends the request. -3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. -4. We run your task and send back the result. - -## Wallet setup - -If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. - -Get the [MetaMask browser extension](https://metamask.io) via the official -site only. Create a new wallet, save the seed phrase somewhere offline, set -a password. -By default, most wallets only show Ethereum. You need to add **Base** (the -network we accept payments on) so your wallet can hold USDC there. -Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and -pay with credit card, bank, etc. The USDC lands directly in your wallet. -In MetaMask: click the account menu → **Account details** → **Private keys** -→ enter your password → copy. That string (starts with `0x`) is your -`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in -their account settings. - - Wallets hold real money, and anyone with the private key can drain it. Be - careful with your keys. - -## Advanced: bring your own x402 client - -For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: - -```python Python -from x402 import x402Client -from x402.mechanisms.evm import EthAccountSigner -from x402.mechanisms.evm.exact.register import register_exact_evm_client -from eth_account import Account -from browser_use_sdk.v3 import AsyncBrowserUse - -x402 = x402Client() -register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) -client = AsyncBrowserUse(x402=x402) - -``` - -```typescript TypeScript -import { x402Client } from "@x402/fetch"; -import { ExactEvmScheme } from "@x402/evm"; -import { privateKeyToAccount } from "viem/accounts"; -import { BrowserUse } from "browser-use-sdk/v3"; - -const x402 = new x402Client(); -x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); -const client = new BrowserUse({ x402 }); +} ``` -## Troubleshooting - -Two likely causes: - -- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. -- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). - - - You haven't installed the optional x402 deps. Run `pip install - "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` - (TypeScript). - - We verified your payment request but couldn't credit your project, so we - deliberately did not settle on-chain. No USDC was moved, so just retry. This - is rare. - - Wait a few seconds. Settlement and credit grant happen in the same request, - but the response may be sent before the credit grant fully commits. If credits - still show `$0` after a few minutes, contact support with your wallet address. - (Conversely, if a payment settles but the request itself then fails, we - automatically reclaim the credits so you aren't charged for nothing.) +Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: -`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. +- `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`) -## Related +**3. Use it** -- [x402 protocol spec](https://www.x402.org) -- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use -- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) +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: -# 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 +``` + +## Option 2: Browser Use CLI -- **`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. +The [Browser Use CLI](https://docs.browser-use.com/open-source/browser-use-cli) is a standalone tool that gives Hermes browser automation through terminal commands. Hermes drives the browser directly via its terminal tool, using Browser Harness and Python helpers through the `browser-use` command. -The 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: +### Setup -```typescript page.tsx -async function handleSend(message: string) { - const session = await createSession(); +**1. Install the CLI** - router.push( - `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` - ); -} +```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 ``` ---- - -## Summary - -| 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 | +### 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. # Agent Sign Up for Browser Use Source: https://docs.browser-use.com/cloud/agent-signup @@ -2692,16 +1267,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](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). +See the [API V4 quick start](https://docs.browser-use.com/cloud/agent/quickstart). ## Claim the account @@ -2734,171 +1309,6 @@ browser-use auth status Replace `bu_...` with the key returned by the REST flow. - -# Grow Therapy provider search -Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare - - -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. - -## What you'll build - -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 - ---- - -## Setup - -```python Python -import asyncio -import json -from pydantic import BaseModel -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { z } from "zod"; - -const client = new BrowserUse(); -``` - -## 1. Define the output schema - -```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(), -}); -``` - -## 2. Create a workspace - -```python Python -workspace = await client.workspaces.create(name="grow-therapy-search") -``` -```typescript TypeScript -const workspace = await client.workspaces.create({ name: "grow-therapy-search" }); -``` - -## 3. Search for providers - -```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 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() -``` -```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}`); -} -``` - -## 4. Sweep across locations and specialties - -After the first run caches the search flow, sweep across different parameters at $0 LLM cost: - -```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") -``` -```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`); - } -} -``` - ---- - -## 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 | - -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. - -## Next steps - -- [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. - - # FAQ Source: https://docs.browser-use.com/cloud/faq @@ -2939,26 +1349,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 vs v4 — which should I use? +## V2 or V4 — which should I use? -**Use v4 for new agent integrations.** It is designed for long-horizon work: +Use **V4** for difficult tasks where accuracy matters. It supports: - Run-focused API with a cheap status polling endpoint -- Conversation sessions with queued and interrupting follow-ups +- 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 -V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. - -```python -# v4 (recommended for new agent runs) -from browser_use_sdk.v4 import AsyncBrowserUse - -# v3 (existing session-based integrations) -from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 -``` +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 @@ -3108,7 +1513,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 @@ -3124,7 +1528,6 @@ const share = await client.sessions.createShare(session.id); console.log(share.shareUrl); ``` - # Skills Source: https://docs.browser-use.com/cloud/legacy/skills @@ -3212,7 +1615,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 @@ -3293,7 +1695,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 @@ -3350,7 +1751,6 @@ const result = await client.run( ); ``` - # API Reference Source: https://docs.browser-use.com/cloud/api-v4-overview @@ -3407,61 +1807,6 @@ curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ 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 Reference -Source: https://docs.browser-use.com/cloud/api-reference - - -## Authentication - -All requests require an API key in the `X-Browser-Use-API-Key` header: - -``` -X-Browser-Use-API-Key: bu_your_key_here -``` - -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. - -## Base URL - -``` -https://api.browser-use.com/api/v3 -``` - -## Quick example - -```bash Create a session -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -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 \ - -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 -``` - ---- - -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()`. - -```bash Python -pip install browser-use-sdk -``` -```bash TypeScript -npm install browser-use-sdk -``` - - # API key Source: https://docs.browser-use.com/cloud/api-v2-overview diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 05bab8ad..5952f01e 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -1,6 +1,6 @@ -# Browser Use Cloud SDK +# Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `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 @@ -8,7 +8,11 @@ - 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. -**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. +**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. 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` @@ -21,29 +25,29 @@ 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. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a high-accuracy browser task with Python, TypeScript, or curl. - [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): Run a long-horizon browser agent with one task and a few lines of code. +- [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, then validate the V4 run result in your application. -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Continue the same V4 conversation, workspace, and browser. -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Poll V4 run events incrementally to monitor progress or build a custom UI. -- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Give a V4 run input files and retrieve files the agent creates. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save and test a reusable script, then run it again from the same workspace. -- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the V4 live browser, let a person take over, then continue the same session. +- [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. +- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save, test, and reuse a script in a workspace. +- [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. +- [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. @@ -51,19 +55,13 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## 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. - [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. -- [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. -- [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. -- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. -- [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. ## 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. - [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. -- [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) - [Agent (v2)](https://docs.browser-use.com/cloud/legacy/agent): V2 agent models and file handling. @@ -75,8 +73,5 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## API v4 - [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. -## API v3 -- [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. - ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index a272577c..12e3a4b4 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -1,11 +1,19 @@ --- title: Quick start -description: "State-of-the-art AI browser automation with stealth browsers, CAPTCHA solving, residential proxies, and managed infrastructure." +description: "Run a high-accuracy browser task with Python, TypeScript, or curl." icon: rocket --- +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 +``` + ## 1. Install +Skip this step if you use curl. + ```bash Python pip install browser-use-sdk @@ -15,58 +23,40 @@ 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 +## 2. Run a task ```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -async def main(): - client = AsyncBrowserUse() - created = await client.runs.create("List the top 20 Hacker News posts and their points") - run = await client.runs.wait_for_completion(created.id) - print(run.result) - -asyncio.run(main()) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +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"}' ``` -Want a full working app? Check out the [Chat UI example](/cloud/tutorials/chat-ui). - -## Agent vs Browser - -| | **Agent** | **Browser** | -|---|---|---| -| **Method** | `runs.create()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | `browserSettings` | ✓ | -| custom proxy | `browserSettings` | ✓ | -| profile | `browserSettings` | ✓ | -| recording | `browserSettings` | ✓ | -| workspace & files | ✓ | — | -| follow-up conversation | ✓ | — | -| screen size | `browserSettings` | ✓ | -| 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). + + + Sessions, workspaces, models, and observability. + + + A compact, API V4-first context file for coding agents. + + diff --git a/docs/cloud/tutorials/integrations/claude-code.mdx b/docs/cloud/tutorials/integrations/claude-code.mdx index 50e025e5..ce057a7b 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 cddda63b..4429e413 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -75,15 +75,16 @@ { "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/human-in-the-loop", + "cloud/agent/observability" ] }, { @@ -122,20 +123,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 +179,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 +476,7 @@ }, { "source": "/cloud/tips/data/streaming", - "destination": "/cloud/agent/streaming" + "destination": "/cloud/agent/observability" }, { "source": "/cloud/tips/data/structured-output", @@ -982,7 +988,15 @@ }, { "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": "/tips/integrations/playwright", diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index 806db709..de5fb845 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,9 +237,9 @@ 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 and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `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 @@ -232,7 +247,11 @@ cat > "$CLOUD_INDEX" << 'HEADER' - 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. -**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. +**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. 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` @@ -248,7 +267,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 @@ -272,7 +290,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 4e59cff7..13b4251e 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -5,8 +5,16 @@ Source: https://docs.browser-use.com/cloud/quickstart +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 +``` + ## 1. Install +Skip this step if you use curl. + ```bash Python pip install browser-use-sdk ``` @@ -14,60 +22,35 @@ 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 +## 2. Run a task ```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse - -async def main(): - client = AsyncBrowserUse() - created = await client.runs.create("List the top 20 Hacker News posts and their points") - run = await client.runs.wait_for_completion(created.id) - print(run.result) +from browser_use_sdk.v4 import BrowserUse -asyncio.run(main()) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) +print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +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"}' ``` -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** | `runs.create()` | `browsers.create()` | -| **What it does** | AI agent runs your task | Raw browser via CDP | -| task | ✓ | — | -| model | ✓ | — | -| proxy | `browserSettings` | ✓ | -| custom proxy | `browserSettings` | ✓ | -| profile | `browserSettings` | ✓ | -| recording | `browserSettings` | ✓ | -| workspace & files | ✓ | — | -| follow-up conversation | ✓ | — | -| screen size | `browserSettings` | ✓ | -| 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). - +Sessions, workspaces, models, and observability. +A compact, API V4-first context file for coding agents. # Prompt for Vibecoders Source: https://docs.browser-use.com/cloud/vibecoding @@ -79,566 +62,369 @@ Copy this link and paste it into your coding agent (Cursor, Claude Code, Windsur https://docs.browser-use.com/cloud/llms.txt ``` - -# Introduction +# Run a task Source: https://docs.browser-use.com/cloud/agent/quickstart -The SDK wraps the [API v4 Reference](https://docs.browser-use.com/cloud/api-v4-overview). Create a run, wait for it to finish, then read `result`. +Create 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 +``` ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +from browser_use_sdk.v4 import BrowserUse -client = AsyncBrowserUse() -created = await client.runs.create("List the top 20 Hacker News posts and their points") -run = await client.runs.wait_for_completion(created.id) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) print(run.result) ``` ```typescript TypeScript import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); -const created = await client.runs.create({ - task: "List the top 20 Hacker News posts and their points", +const run = await client.runs.create({ + task: "Find the top Hacker News story", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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 Hacker News posts and their points"}' + -d '{"task":"Find the top Hacker News story"}' ``` -`runs.create()` automatically creates a session and workspace. `wait_for_completion()` / `waitForCompletion()` polls the lightweight [run status endpoint](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status), then fetches the full [run result](https://docs.browser-use.com/cloud/api-v4/runs/get-run) once it reaches `completed`, `failed`, or `cancelled`. +Install the SDK with `pip install browser-use-sdk` or +`npm install browser-use-sdk`. Curl needs no installation. -Use the agent for: +Every new run implicitly creates a **session** and a **workspace**: -- Data extraction and research across many pages -- Form filling, downloads, and multi-step workflows -- Authenticated work with browser profiles -- Long-running tasks that create or consume files -- Follow-up turns that preserve the same conversation, workspace, and live browser + + + -See [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks), [Live messages](https://docs.browser-use.com/cloud/agent/streaming), and [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces) for the main V4 patterns. +Continue the same conversation and browser. +Keep files across runs and sessions. +Poll ordered events while a run is active. + Give this compact context file to your coding agent. # Models Source: https://docs.browser-use.com/cloud/agent/models -Pass `model` when you create a run. These are the models currently shown in the V4 agent UI: +Pass one of these API strings as `model` when creating a run: -| Model | API string | Input | Cache read | Output | Bring your own key | -| ----- | ---------- | ----: | ---------: | -----: | ------------------ | +| 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 | — | -Prices are USD per 1 million tokens using Browser Use's provider keys and include the platform markup. Grok 4.5 requests with 200k or more context use its higher long-context rate. Cache prices are for cache reads; cache writes can cost more. +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). - **MiniMax M3** is the default and the cheapest choice for simple tasks. Use **Claude Opus 5** when maximum reasoning quality matters. + **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** + when accuracy matters most. ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Compare the top three project-management tools for a 20-person startup", +run = client.runs.create( + "Compare three project-management tools", model="claude-opus-5", ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Compare the top three project-management tools for a 20-person startup", +const run = await client.runs.create({ + task: "Compare three project-management tools", model: "claude-opus-5", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); ``` ```bash curl -curl -X POST https://api.browser-use.com/api/v4/runs \ - -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": "Compare the top three project-management tools", "model": "claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"claude-opus-5"}' ``` ## Bring your own key -Add an Anthropic, OpenAI, or Google key under **Settings → API Keys → Bring Your Own Key**. V4 automatically uses a matching project key for that provider; there is no `use_own_key` / `useOwnKey` request flag. - -With your own key, you pay the provider directly and Browser Use charges a 0.2× orchestration fee based on provider list token prices. If no matching key is configured, V4 uses Browser Use's provider key and the rates in the table above. - -Grok 4.5 and MiniMax M3 currently use Browser Use-managed keys only. - +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 -V4 returns the agent's final answer as a string in `run.result`. Ask the agent for JSON only, then validate it with Pydantic or Zod in your application. - - V4 does not currently accept an `output_schema` / `outputSchema` request field. Validation happens client-side. +V4 returns `run.result` as a string. Ask for JSON only, then validate it +client-side: ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse from pydantic import BaseModel -class Post(BaseModel): - name: str +class Story(BaseModel): + title: str points: int - comments: int - -class HNPosts(BaseModel): - posts: list[Post] -client = AsyncBrowserUse() -created = await client.runs.create( - """ - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - """ +run = client.runs.create( + 'Find the top HN story. Return only {"title":"...","points":0}.' ) -run = await client.runs.wait_for_completion(created.id) -posts = HNPosts.model_validate_json(run.result or "{}") - -for post in posts.posts: - print(f"{post.name} ({post.points} pts)") +run = client.runs.wait_for_completion(run.id) +story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; -const HNPosts = z.object({ - posts: z.array(z.object({ - name: z.string(), - points: z.number(), - comments: z.number(), - })), +const Story = z.object({ + title: z.string(), + points: z.number(), }); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: ` - List the top 20 Hacker News posts. - Return JSON only in this shape: - {"posts": [{"name": "string", "points": 0, "comments": 0}]} - `, +const run = await client.runs.create({ + task: 'Find the top HN story. Return only {"title":"...","points":0}.', }); -const run = await client.runs.waitForCompletion(created.id); -const posts = HNPosts.parse(JSON.parse(run.result ?? "{}")); - -for (const post of posts.posts) { - console.log(`${post.name} (${post.points} pts)`); -} +const result = await client.runs.waitForCompletion(run.id); +const story = Story.parse(JSON.parse(result.result ?? "{}")); ``` -For strict production flows, handle JSON parse or validation failures and retry with a follow-up message that includes the validation error. +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. +# Sessions +Source: https://docs.browser-use.com/cloud/agent/sessions -# Follow-up tasks -Source: https://docs.browser-use.com/cloud/agent/follow-up-tasks +A **session** holds the agent's conversation and can reuse its live browser. +Every run creates one implicitly unless you pass an existing session ID. -Every run automatically creates a session. Pass its `session_id` / `sessionId` to create an explicit follow-up turn: + + + -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() +Pass `session_id` / `sessionId` to continue: -first = await client.runs.create( - "Go to amazon.com, search for laptops, and open the first result" -) -first_result = await client.runs.wait_for_completion(first.id) +```python Python +first = client.runs.create("Open Hacker News") +client.runs.wait_for_completion(first.id) -follow_up = await client.runs.create( - "Extract the customer reviews", +follow_up = client.runs.create( + "Now summarize the top story", session_id=first.session_id, ) -follow_up_result = await client.runs.wait_for_completion(follow_up.id) -print(follow_up_result.result) +result = client.runs.wait_for_completion(follow_up.id) +print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); - const first = await client.runs.create({ - task: "Go to amazon.com, search for laptops, and open the first result", + task: "Open Hacker News", }); await client.runs.waitForCompletion(first.id); const followUp = await client.runs.create({ - task: "Extract the customer reviews", + task: "Now summarize the top story", sessionId: first.sessionId, }); const result = await client.runs.waitForCompletion(followUp.id); console.log(result.result); ``` -The follow-up restores the agent's conversation context and workspace. It also reuses the live browser when one is still available. - -There is no separate empty-session creation step in V4: - -- Omit `session_id` / `sessionId` to create a new session implicitly. -- Pass a previous session ID to continue it explicitly. -- Pass only `workspace_id` / `workspaceId` to start a new conversation that shares existing files. - -## Queue a follow-up - -Use `sessions.send_message()` / `sessions.sendMessage()` when a run may still be busy. The message runs immediately if the session is idle, or waits for the current run to finish. - -```python Python -queued = await client.sessions.send_message( - first.session_id, - "Also compare the warranty options", -) -``` -```typescript TypeScript -const queued = await client.sessions.sendMessage(first.sessionId, { - text: "Also compare the warranty options", -}); -``` - -Set `interrupt=True` / `interrupt: true` to cancel the active run and start the queued message as soon as possible. A queued response can initially have no run ID; use [Get session](https://docs.browser-use.com/cloud/api-v4/sessions/get-session) or [List runs](https://docs.browser-use.com/cloud/api-v4/runs/list-runs) to discover the new run once it starts. - -See [Queue session message](https://docs.browser-use.com/cloud/api-v4/sessions/queue-session-message) for the full request shape. - - -# Live messages -Source: https://docs.browser-use.com/cloud/agent/streaming - - -V4 exposes an ordered event stream for each run. Poll with `after` set to the previous response's `next_after` / `nextAfter` so you only receive new events. - -Each event has `id`, `ts`, `type`, and `data`. Event types include run lifecycle updates, model calls, browser readiness, tool activity, artifacts, and completion. - -```python Python -import asyncio -from browser_use_sdk.v4 import AsyncBrowserUse - -TERMINAL = {"completed", "failed", "cancelled"} - -client = AsyncBrowserUse() -created = await client.runs.create("Find the top story on Hacker News") - -after = None -while True: - page = await client.runs.events(created.id, after=after, limit=100) - for event in page.events: - print(event.type, event.data) - if page.next_after is not None: - after = page.next_after - - status = await client.runs.status(created.id) - if status.status.value in TERMINAL: - break - await asyncio.sleep(1) - -run = await client.runs.get(created.id) -print(run.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const TERMINAL = new Set(["completed", "failed", "cancelled"]); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find the top story on Hacker News", -}); - -let after: number | undefined; -while (true) { - const page = await client.runs.events(created.id, { after, limit: 100 }); - for (const event of page.events) { - console.log(event.type, event.data); - } - if (page.nextAfter != null) after = page.nextAfter; - - const { status } = await client.runs.status(created.id); - if (TERMINAL.has(status)) break; - await new Promise((resolve) => setTimeout(resolve, 1000)); -} - -const run = await client.runs.get(created.id); -console.log(run.result); -``` - -The status endpoint is intentionally tiny and cheap to poll. Fetch the full run only after its status is terminal. - -## Cancel a run - -```python Python -cancelled = await client.runs.cancel(created.id) -print(cancelled.status) -``` -```typescript TypeScript -const cancelled = await client.runs.cancel(created.id); -console.log(cancelled.status); -``` - -Cancelling a run does not delete its session. You can send another turn with the same session ID. - -## Related - -- [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) — event response and cursor fields -- [Get run status](https://docs.browser-use.com/cloud/api-v4/runs/get-run-status) — lightweight poll target -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks) — continue or queue work in the same session - +- Omit the session ID for a new conversation. +- Reuse it for a follow-up with the same context and workspace. +- Pass only a [workspace ID](https://docs.browser-use.com/cloud/agent/workspaces) for a fresh conversation + that shares files. # Workspaces & files Source: https://docs.browser-use.com/cloud/agent/workspaces -Every V4 run has a workspace. You can let the API create one automatically, create one yourself, or reuse an existing workspace across otherwise independent sessions. +A **workspace** is a persistent filesystem. A run can read attached inputs, +create files, and share those files with later sessions. -## Upload and attach input files + + + -Uploading stores the file in the workspace and returns an upload ID. Pass that ID in `attached_file_ids` / `attachedFileIds` to make the file available to a specific run. +## Upload and attach a file ```python Python -from browser_use_sdk.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="company-research") -uploaded = await client.workspaces.upload(workspace.id, "people.csv") +workspace = client.workspaces.create(name="research") +uploaded = client.workspaces.upload(workspace.id, "people.csv") -created = await client.runs.create( - "Read the attached 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], ) -run = await client.runs.wait_for_completion(created.id) -print(run.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; - -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "company-research" }); -const uploaded = await client.workspaces.upload(workspace.id, "people.csv"); - -const created = await client.runs.create({ - task: "Read the attached people.csv and tell me who works at Google", - workspaceId: workspace.id, - attachedFileIds: [uploaded[0].id], +const workspace = await client.workspaces.create({ + name: "research", }); -const run = await client.runs.waitForCompletion(created.id); -console.log(run.result); -``` - -You can upload up to 10 files in one helper call. A run can attach up to 20 upload IDs. - -```python Python -uploaded = await client.workspaces.upload( - workspace.id, - "data.csv", - "config.json", - "image.png", -) -``` -```typescript TypeScript const uploaded = await client.workspaces.upload( workspace.id, - "data.csv", - "config.json", - "image.png", + "people.csv", ); + +const run = await client.runs.create({ + task: "Find everyone in the CSV who works at Google", + workspaceId: workspace.id, + attachedFileIds: [uploaded[0].id], +}); ``` - Attachments are turn-scoped. Reusing a workspace does not automatically attach every uploaded file to every later run. +Attachments are turn-scoped. Reusing a workspace does not automatically attach +every upload to later runs. -## Retrieve files the agent creates +## Retrieve created files -Ask the agent to save its output in the workspace, then list files with temporary download URLs: +Ask the agent to save its output, then list the workspace: ```python Python -created = await client.runs.create( - "Save the top three Hacker News posts as outputs/posts.json", - workspace_id=workspace.id, -) -await client.runs.wait_for_completion(created.id) - -files = await client.workspaces.files( +files = client.workspaces.files( workspace.id, - prefix="outputs/", include_urls=True, ) for file in files.files: print(file.path, file.url) ``` ```typescript TypeScript -const created = await client.runs.create({ - task: "Save the top three Hacker News posts as outputs/posts.json", - workspaceId: workspace.id, -}); -await client.runs.waitForCompletion(created.id); - -const files = await client.workspaces.files(workspace.id, { - prefix: "outputs/", - includeUrls: true, -}); +const files = await client.workspaces.files( + workspace.id, + { includeUrls: true }, +); for (const file of files.files) { console.log(file.path, file.url); } ``` -Download URLs expire after 60 seconds, so request them immediately before downloading. Use `cursor` / `next_cursor` (`nextCursor` in TypeScript) to paginate large workspaces. - -## Reuse a workspace - -- Pass neither ID to `runs.create()` to create a new session and workspace. -- Pass `session_id` / `sessionId` to continue the same conversation and workspace. -- Pass only `workspace_id` / `workspaceId` to start a fresh conversation with existing files. - -See [Upload workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and response fields. - +Download URLs expire after 60 seconds. See [Upload workspace +files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace +files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. # Deterministic rerun Source: https://docs.browser-use.com/cloud/agent/cache-script -For repeated workflows, create a dedicated workspace and ask the agent to turn its successful process into a script. The important part is explicit: tell it to reproduce what it just did, test the script, and save instructions for the next run. - -You can create the workspace in the dashboard or through the API: +Create one [workspace](https://docs.browser-use.com/cloud/agent/workspaces) for the workflow, then use +these prompts with the same `workspace_id` / `workspaceId`. The [run +code](https://docs.browser-use.com/cloud/agent/quickstart) stays exactly the same. -```python Python -from browser_use_sdk.v4 import AsyncBrowserUse +## First run -client = AsyncBrowserUse() -workspace = await client.workspaces.create(name="hn-scraper") - -created = await client.runs.create( - """ - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - """, - workspace_id=workspace.id, -) -first = await client.runs.wait_for_completion(created.id) -print(first.result) -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; +```text +Complete this task: get the top five Hacker News stories as JSON. -const client = new BrowserUse(); -const workspace = await client.workspaces.create({ name: "hn-scraper" }); - -const created = await client.runs.create({ - task: ` - Get the top five Hacker News stories as JSON. - Then create helper functions or a script that performs exactly what you did. - Test it, save it as scripts/hn_top.py, and save reuse instructions in - scripts/README.md. - `, - workspaceId: workspace.id, -}); -const first = await client.runs.waitForCompletion(created.id); -console.log(first.result); +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, start a new run in the same workspace and tell the agent to use the saved script: +## Later runs -```python Python -created = await client.runs.create( - "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspace_id=workspace.id, -) -rerun = await client.runs.wait_for_completion(created.id) -print(rerun.result) -``` -```typescript TypeScript -const created = await client.runs.create({ - task: "Run scripts/hn_top.py for the top 10 stories. Use the existing script; fix and retest it only if needed.", - workspaceId: workspace.id, -}); -const rerun = await client.runs.waitForCompletion(created.id); -console.log(rerun.result); +```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 pattern gives the agent a fast, inspectable path and lets it repair the script when the website changes. Keep one workspace per workflow so scripts, fixtures, outputs, and instructions stay together. - - V4 does not automatically turn a task into a cached $0-LLM execution. Each rerun starts an agent, so it still has token cost. The saved script usually makes the run faster and cheaper, but you should measure it for your workflow. - +This still starts an agent and uses tokens. The saved script gives the agent a +faster, more predictable path; it is not automatic zero-LLM execution. # Human in the loop Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop -Use a human checkpoint for approvals, payments, complex authentication, or reviewing work before the agent continues. - -The run's `browser.ready` event contains a `live_view_url`. After the first turn stops at a safe checkpoint, open that URL, let the human interact, then send a follow-up with the same session ID. +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.v4 import AsyncBrowserUse - -client = AsyncBrowserUse() -created = await client.runs.create( - "Find noise-cancelling headphones on Amazon and stop before selecting a product" +events = client.runs.events(run.id, limit=100) +ready = next( + event for event in events.events + if event.type == "browser.ready" ) -await client.runs.wait_for_completion(created.id) - -events = await client.runs.events(created.id, limit=100) -ready = next(event for event in events.events if event.type == "browser.ready") -live_url = ready.data["live_view_url"] -print(f"Open this live browser: {live_url}") - -input("Press Enter after selecting a product...") +print(ready.data["live_view_url"]) -follow_up = await client.runs.create( - "Get the selected product's name, price, and rating", - session_id=created.session_id, +# After the human finishes: +next_run = client.runs.create( + "Continue from the current page", + session_id=run.session_id, ) -result = await client.runs.wait_for_completion(follow_up.id) -print(result.result) ``` ```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v4"; -import * as readline from "node:readline/promises"; +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); -const client = new BrowserUse(); -const created = await client.runs.create({ - task: "Find noise-cancelling headphones on Amazon and stop before selecting a product", +// After the human finishes: +const nextRun = await client.runs.create({ + task: "Continue from the current page", + sessionId: run.sessionId, }); -await client.runs.waitForCompletion(created.id); +``` -const events = await client.runs.events(created.id, { limit: 100 }); -const ready = events.events.find((event) => event.type === "browser.ready"); -const liveUrl = ready?.data.live_view_url; -console.log(`Open this live browser: ${liveUrl}`); +The same session preserves the conversation and workspace and reuses the live +browser while it is available. Treat live-view URLs as credentials. -const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); -await rl.question("Press Enter after selecting a product..."); -rl.close(); +# Observability +Source: https://docs.browser-use.com/cloud/agent/observability -const followUp = await client.runs.create({ - task: "Get the selected product's name, price, and rating", - sessionId: created.sessionId, -}); -const result = await client.runs.waitForCompletion(followUp.id); -console.log(result.result); -``` -The browser is kept alive for follow-ups when possible. If it has expired, V4 restores the conversation and workspace but provisions a new browser, so complete the human step before the live browser's timeout. +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 or after - Treat live-view URLs as credentials. Anyone with the URL can interact with the browser while it is active. + 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; -See [Get run events](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) for the event response. + 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](https://docs.browser-use.com/cloud/api-v4/runs/get-run-events) +for the complete response shape. -# Introduction Stealth +# Stealth Source: https://docs.browser-use.com/cloud/browser/stealth @@ -656,223 +442,171 @@ Every cloud browser session runs in a hardened Chromium fork with stealth enable 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. - # Proxies Source: https://docs.browser-use.com/cloud/browser/proxies -A US residential proxy is active by default on every browser. To route through a different country, set `proxy_country_code`. See the [API reference](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session) for all supported country codes. +A US residential proxy is enabled by default. Set `browser_settings` / +`browserSettings` when you create a V4 run to choose another country: ```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( + "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", + 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", + 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", + } }, ) ``` ```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", + browserSettings: { + customProxy: { + host: "proxy.example.com", + port: 8080, + username: "user", + password: "pass", + }, }, }); ``` +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. # Live preview & recording Source: https://docs.browser-use.com/cloud/browser/live-preview - Want a ready-made UI? See the [Chat UI tutorial](https://docs.browser-use.com/cloud/tutorials/chat-ui). - -`liveUrl` is returned on session creation. +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 = AsyncBrowserUse() -session = await client.sessions.create(task="Check how many GitHub stars browser-use has") -print(session.live_url) +client = BrowserUse() +run = client.runs.create("Find the top Hacker News story") +run = client.runs.wait_for_completion(run.id) + +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", }); -console.log(session.liveUrl); -``` +await client.runs.waitForCompletion(run.id); -`liveUrl` is also returned when creating a standalone browser session: - -```python Python -browser = await client.browsers.create() -print(browser.live_url) -``` -```typescript TypeScript -const browser = await client.browsers.create(); -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 +Poll [run events](https://docs.browser-use.com/cloud/agent/observability) if you need the URL as soon as +the browser starts. -Useful for human interaction or to see live what's happening. +## Embed the live browser ```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: +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. -``` -Content-Security-Policy: frame-src 'self' https://live.browser-use.com; -``` +## Recording -For responsive sizing, use CSS instead of fixed dimensions: - -```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 -``` - -## 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", + browserSettings: { 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](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 - - +The MP4 becomes available in the Dashboard after the browser stops. API runs +default to recording off, and Zero Data Retention projects never record. # Playwright, Puppeteer, Selenium Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium @@ -880,23 +614,26 @@ 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) + 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). + +## WebSocket URL Connect with a single URL. All configuration is passed as query parameters. ### Playwright ```python Python -from playwright.async_api import async_playwright +from playwright.sync_api import sync_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) +with sync_playwright() as p: + browser = 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() + page.goto("https://example.com") + print(page.title()) + browser.close() # Browser is automatically stopped when the WebSocket disconnects ``` ```typescript TypeScript @@ -928,22 +665,8 @@ 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. +Selenium's `debugger_address` only supports local `host:port` connections. +Use Playwright or Puppeteer for remote CDP over WebSocket. ## Query parameters @@ -956,453 +679,144 @@ with sync_playwright() as p: | `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. - + Close the CDP connection when done. Browsers left running continue to incur + charges until their timeout expires. # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse +A profile persists cookies, local storage, and login state across browsers. +Create or select one under [Dashboard → Profiles](https://cloud.browser-use.com/settings?tab=profiles), +then pass its ID in V4 browser settings: -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) +```python Python +from browser_use_sdk.v4 import BrowserUse -# Always stop the session to persist profile state -await client.sessions.stop(session.id) +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", + browserSettings: { profileId: "YOUR_PROFILE_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) +const result = await client.runs.waitForCompletion(run.id); +console.log(result.result); ``` -```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. - - 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. +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. +For the fastest setup, [sync an existing local login](https://docs.browser-use.com/cloud/guides/profile-sync). # Sync local and cloud cookies Source: https://docs.browser-use.com/cloud/guides/profile-sync +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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, }); ``` +The profile supplies cookies and local storage without putting credentials in +the prompt. Re-sync when the site's login expires. # 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. +The most reliable options are a saved profile or a human checkpoint. -```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 }); +## Reuse a logged-in profile -// 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. +[Sync your local login](https://docs.browser-use.com/cloud/guides/profile-sync), then load that profile in +the run: ```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, +run = client.runs.create( + "Download my latest invoice", + browser_settings={"profileId": "YOUR_PROFILE_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", + browserSettings: { profileId: "YOUR_PROFILE_ID" }, +}); ``` -See [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop) for more patterns. - ---- - -## 3. Agent Mail +This avoids another 2FA challenge while the site's cookies remain valid. -When 2FA sends a code via email, the agent can read it automatically using Agent Mail — a built-in email inbox for each session. +## Let a human take over -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, open its `live_view_url`, 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 +client.runs.wait_for_completion(first.id) -If you're automating on behalf of your users and they need to receive 2FA codes: - -1. **Email forwarding:** Have your client set up an email forwarding rule — forward all emails from the service (e.g., `noreply@bank.com`) to a dedicated inbox (a Gmail address or an Agent Mail address). -2. **Give the agent access:** The agent reads the forwarded 2FA code from that inbox during the task. - -This way, your client's real email stays private — the agent only sees the forwarded verification emails. - -### Connect external email via Composio - -You can also give the agent access to an existing Gmail account using [Composio](https://composio.dev) in the Browser Use dashboard. Once connected, the agent can read emails directly from that account to retrieve 2FA codes. - ---- - -## 4. TOTP secret in prompt - -If the site uses an authenticator app (Google Authenticator, Authy, etc.), you can pass the TOTP secret to the agent. Our agent can execute Python code, so it uses the `pyotp` library to generate fresh 6-digit codes on the fly. - -When you set up 2FA on a site, instead of only scanning the QR code, also copy the **secret key** (usually shown as "manual entry" or "can't scan the QR code?"). This is a long base32 string like `JBSWY3DPEHPK3PXP`. - -```python Python -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() - -# The TOTP secret from your authenticator setup — NOT the 6-digit code -totp_secret = "JBSWY3DPEHPK3PXP" - -result = await client.run( - f""" - Log into example.com with username user@example.com and password mypassword. - When prompted for a 2FA code, generate one using pyotp: - - import pyotp - totp = pyotp.TOTP("{totp_secret}") - code = totp.now() - - Enter the generated code. - """, +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() +const first = await client.runs.create({ + task: "Open the login page and stop at the 2FA prompt", +}); +await client.runs.waitForCompletion(first.id); - Enter the generated code.`, -); -console.log(result.output); +const nextRun = await client.runs.create({ + task: "Continue after login and download the invoice", + 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](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. # Claude Code Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code @@ -1474,30 +888,6 @@ browser-use auth status 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. -## 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](https://docs.browser-use.com/cloud/guides/x402). - - # Claude Managed Agents Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-managed-agents @@ -1584,7 +974,6 @@ The agent starts a named cloud browser, runs Python helper snippets through `bro 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 @@ -1623,1007 +1012,193 @@ Open `~/.openclaw/openclaw.json` and add a `browser-use` profile: }, }, }, -} -``` - -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 -uv tool install browser-use -``` - -**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 drive pages through Browser Harness and Python helpers. - -For the complete CLI reference, see the [Browser Use CLI docs](https://docs.browser-use.com/open-source/browser-use-cli). - - -# Hermes Agent -Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent - - -[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. - -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. - -## 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. - -### Setup - -**1. Get your API key** - -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). - -Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - -**2. Configure Hermes** - -Run the setup wizard: - -```bash -hermes setup tools -``` - -Select **Browser Automation**, then **Browser Use**, and paste your API key when prompted. - -Or configure manually — add your key to `~/.hermes/.env`: - -```bash -BROWSER_USE_API_KEY=your_key_here -``` - -And set the provider in `~/.hermes/config.yaml`: - -```yaml -browser: - cloud_provider: browser-use -``` - -**3. Use it** - -Just chat with Hermes — any browsing tasks automatically route through Browser Use cloud browsers: - -``` -> Find the top trending repositories on GitHub today and summarize them -``` - -## Option 2: Browser Use CLI - -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. - -### Setup - -**1. Install the CLI** - -```bash -uv tool install browser-use -``` - -**2. Verify the installation** - -```bash -browser-use doctor -``` - -**3. Register the skill** - -Register the Browser Use skill with the installed CLI: - -```bash -browser-use skill install -``` - -Or ask Hermes directly in chat to install it. - -**4. Authenticate for cloud browsers** - -Authenticate with your API key: - -```bash -browser-use auth login -``` - -Or let the agent provision one itself — see [Agent Self-Registration](#agent-self-registration) below. - -**5. Use it** - -Once the skill is loaded, Hermes can drive the browser through CLI commands via its terminal tool: - -``` -> Use browser-use to open github.com/trending and summarize the top repos -``` - -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). - -## Agent Self-Registration - -Hermes can provision its own Browser Use API key autonomously — no human interaction needed. This works with both options above. - -Install the Browser Use CLI and skill: - -```bash -uv tool install browser-use -browser-use skill install -``` - -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** - -For the cloud browser backend (Option 1): - -```bash -hermes config set BROWSER_USE_API_KEY -``` - -For CLI mode (Option 2), put the key in the agent's shell environment: - -```bash -export BROWSER_USE_API_KEY=bu_... -browser-use auth status -``` - -### 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. - - -# MCP Server -Source: https://docs.browser-use.com/cloud/guides/mcp-server - - -``` -https://api.browser-use.com/v3/mcp -``` - -Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). - -## Claude Code - -```bash -claude mcp add -t http -H "x-browser-use-api-key: YOUR_API_KEY" browser-use https://api.browser-use.com/v3/mcp -``` - -## Claude Desktop - -Add to `claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Cursor - -Add to `.cursor/mcp.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "url": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Windsurf - -Add to `~/.codeium/windsurf/mcp_config.json`: - -```json -{ - "mcpServers": { - "browser-use": { - "serverUrl": "https://api.browser-use.com/v3/mcp", - "headers": { - "x-browser-use-api-key": "YOUR_API_KEY" - } - } - } -} -``` - -## Available Tools - -| Tool | Description | -|------|-------------| -| `run_session` | Create a session and run a task. Supports `keep_alive`, `model` (`claude-sonnet-4.6`, `claude-opus-4.6`, `gpt-5.4-mini`), `output_schema`, and `profile_id`. | -| `get_session` | Poll session status and output. Returns status, step count, cost breakdown, and live URL. | -| `send_task` | Send a follow-up task to an idle keep-alive session. | -| `stop_session` | Stop a session. `strategy: "task"` stops only the task, `"session"` destroys the sandbox. | -| `get_session_messages` | Get the agent's messages — browser actions, reasoning, and results. | -| `list_sessions` | List recent sessions with status and cost. | -| `list_browser_profiles` | List browser profiles for authenticated tasks. | - - -# Webhooks -Source: https://docs.browser-use.com/cloud/guides/webhooks - - -Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks). - -## Events - -| Event | When | -|-------|------| -| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) | -| `test` | Webhook test ping | - -## Payload - -```json -{ - "type": "agent.task.status_update", - "timestamp": "2025-01-15T10:30:00Z", - "payload": { - "task_id": "task_abc123", - "session_id": "session_xyz", - "status": "idle", - "metadata": {} - } -} -``` - -## Signature verification - -Every webhook request includes two headers: - -- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload -- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent - -The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks. - -```python Python -import hashlib -import hmac -import json -import time - -def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool: - # Reject requests older than 5 minutes - try: - ts = int(timestamp) - except (ValueError, TypeError): - return False - if abs(time.time() - ts) > 300: - return False - payload = json.loads(body) - message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" - expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest() - return hmac.compare_digest(expected, signature) -``` -```typescript TypeScript -import { createHmac, timingSafeEqual } from "crypto"; - -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { - return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} - -function verifyWebhook(body: string, signature: string, timestamp: string, secret: string): boolean { - // Reject requests older than 5 minutes - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false; - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", secret).update(message).digest("hex"); - return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); -} -``` - -## Example: Express webhook handler - -```typescript -import express from "express"; -import { createHmac, timingSafeEqual } from "crypto"; - -const app = express(); -app.use(express.raw({ type: "application/json" })); - -const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; - -function sortKeys(obj: unknown): unknown { - if (Array.isArray(obj)) return obj.map(sortKeys); - if (obj !== null && typeof obj === "object") { - return Object.keys(obj as object) - .sort() - .reduce((acc, key) => { - (acc as Record)[key] = sortKeys((obj as Record)[key]); - return acc; - }, {} as Record); - } - return obj; -} - -app.post("/webhook", (req, res) => { - const signature = req.headers["x-browser-use-signature"] as string; - const timestamp = req.headers["x-browser-use-timestamp"] as string; - - if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { - return res.status(401).send("Request too old"); - } - - const body = req.body.toString(); - const payload = JSON.parse(body); - const message = `${timestamp}.${JSON.stringify(sortKeys(payload))}`; - const expected = createHmac("sha256", WEBHOOK_SECRET).update(message).digest("hex"); - - if (!timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) { - return res.status(401).send("Invalid signature"); - } - - if (payload.type === "agent.task.status_update") { - const { task_id, status, session_id } = payload.payload; - console.log(`Task ${task_id} is now ${status}`); - } - - res.status(200).send("OK"); -}); - -app.listen(3000); -``` - -## Example: FastAPI webhook handler - -```python -from fastapi import FastAPI, Request, HTTPException -import hashlib -import hmac -import json -import os -import time - -app = FastAPI() - -WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"] - -@app.post("/webhook") -async def handle_webhook(request: Request): - body = await request.body() - signature = request.headers.get("x-browser-use-signature", "") - timestamp = request.headers.get("x-browser-use-timestamp", "") - - # Reject requests older than 5 minutes - try: - ts = int(timestamp) - except (ValueError, TypeError): - raise HTTPException(status_code=401, detail="Invalid timestamp") - if abs(time.time() - ts) > 300: - raise HTTPException(status_code=401, detail="Request too old") - - payload = json.loads(body) - message = f"{timestamp}.{json.dumps(payload, separators=(',', ':'), sort_keys=True)}" - expected = hmac.new(WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest() - - if not hmac.compare_digest(expected, signature): - raise HTTPException(status_code=401, detail="Invalid signature") - - if payload["type"] == "agent.task.status_update": - task_id = payload["payload"]["task_id"] - status = payload["payload"]["status"] - print(f"Task {task_id} is now {status}") - - return {"status": "ok"} -``` - - For local development, use a tunneling tool like [ngrok](https://ngrok.com) to expose your local server: `ngrok http 3000`. Then set the ngrok URL as your webhook endpoint in the dashboard. - - -# x402 (pay-per-request) -Source: https://docs.browser-use.com/cloud/guides/x402 - - - - -[x402](https://www.x402.org) is a payment protocol [created by Coinbase](https://www.coinbase.com/developer-platform/discover/launches/x402) that lets APIs, or AI agents, charge for requests directly with crypto. - -x402 lets your code, or an autonomous AI agent, pay Browser Use Cloud directly with cryptocurrency. No account signup, no credit card, and no API key is needed. Your wallet is your identity. - - -**New to crypto?** Here's the gist: - -- **USDC** is a stablecoin pegged 1:1 to the US dollar. 1 USDC = $1. -- **Base** is a low-fee blockchain network operated by Coinbase. Sending a payment costs fractions of a cent. -- **Wallet** = a public address (your "username") and a private key (your "password"). The private key signs payments. -- You'll need at least $5 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch. - - -**Three ways to start, ranked by laziness:** - -One command. Claude does the wallet setup, funding walkthrough, and -verification for you. -One line in your Python or TypeScript app. Bring your own wallet. -Skip the SDK. Sign EIP-3009, send `X-PAYMENT` header. - -## Claude Code quickstart - -The fastest path. Install the [x402 skill](https://github.com/browser-use/browser-use/tree/main/skills/x402), and Claude walks you through everything: - -```bash -npx skills add https://github.com/browser-use/browser-use --skill x402 -``` - -Then in Claude Code: - -``` -> /x402 -``` - -Claude generates (or imports) a wallet, walks you through funding it via Coinbase, writes `BROWSER_USE_X402_PRIVATE_KEY` to your `.env`, installs the SDK, and runs a verification task. Total: ~2 minutes if you have a crypto wallet. - - Already have a Browser Use Cloud account? The skill detects this and switches - to **top-up mode**, adding credits to that existing account instead of - creating a new, wallet-keyed one. - -## SDK quickstart - -The Browser Use SDK has built-in x402 support. Pass a wallet private key, and you're done. - -```bash Python -pip install "browser-use-sdk[x402]" -``` -```bash TypeScript -npm install browser-use-sdk @x402/fetch @x402/evm viem -``` - -```python Python -import asyncio -from browser_use_sdk.v3 import AsyncBrowserUse - -async def main(): - client = AsyncBrowserUse(x402_private_key="0x...") # EVM wallet w/ USDC on Base - result = await client.run("Go to example.com and tell me the heading.") - print(result.output) - -asyncio.run(main()) -``` - -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse({ x402PrivateKey: "0x..." }); // EVM wallet w/ USDC on Base -const result = await client.run("Go to example.com and tell me the heading."); -console.log(result.output); -``` - -Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely: - -```python Python -client = AsyncBrowserUse() # auto-detects from env -``` -```typescript TypeScript -const client = new BrowserUse(); // auto-detects from env -``` - - Python: x402 is async-only. Use `AsyncBrowserUse`, not `BrowserUse`. - -## Raw HTTP quickstart - -Use this if you're in a language we don't ship an SDK for (Go, Rust, Ruby, etc.), or if you want to use other x402 APIs from the same client library. Hit `https://x402.api.browser-use.com` directly with any [x402 client library](https://github.com/coinbase/x402#all-available-reference-sdks): - -```python -import asyncio - -from x402 import x402Client -from x402.http.clients import x402HttpxClient -from x402.mechanisms.evm import EthAccountSigner -from x402.mechanisms.evm.exact.register import register_exact_evm_client -from eth_account import Account - -async def main(): - client = x402Client() - register_exact_evm_client(client, EthAccountSigner(Account.from_key("0x..."))) - - async with x402HttpxClient(client, timeout=120.0) as http: - response = await http.post( - "https://x402.api.browser-use.com/api/v3/sessions", - json={"task": "..."}, - ) - print(response.status_code, response.text[:500]) - -asyncio.run(main()) -``` - -`https://x402.api.browser-use.com` exposes the same routes as `https://api.browser-use.com`. It supports every `/api/v2/*` and `/api/v3/*` route, gated by an x402 challenge instead of API key auth. - -## What you need - -- **EVM wallet** (MetaMask, Rabby, Coinbase Wallet, etc.) with its private key available to your app -- **USD Coin (USDC) on Base mainnet** -- **Default top-up:** `$5.00` USDC per request (`$1.00` minimum for budget-constrained wallets) - -You do **not** need ETH for gas. We use [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009), so you sign offchain, and the facilitator pays gas. - - -## Pricing and credits - -Each x402 payment adds `$5` of credits to your project by default (or `$1` if your wallet falls back to the smaller option). When credits hit zero, the next request returns `402`, and the SDK automatically signs another payment to keep going. **You don't manage top-ups manually; just make sure your wallet has enough USDC for your expected usage.** - - **Mid-task drain still terminates the task.** Browser Use sessions run on a - worker that doesn't see x402, so once a long-running task starts and burns - through its credits, it stops with `INSUFFICIENT_CREDITS` — it does not pause - and wait for the next x402 payment. The `$5` default exists so most tasks - complete without hitting this; for expensive models (e.g. Opus) or long - sessions, pre-fund with multiple requests before kicking off the task. - -See the [pricing page](https://browser-use.com/pricing) for model and browser costs. - -## Topping up an existing account - -If you already have a Browser Use API key (for example, one created via the dashboard or the agent signup REST flow), you can use x402 to add credits to **that** account instead of creating a new project based on your crypto wallet. Send your existing API key alongside the payment: - -```python Python -import asyncio - -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse( - api_key="bu_...", # existing API key getting topped up - x402_private_key="0x...", # wallet that pays - base_url="https://x402.api.browser-use.com/api/v3", -) -async def main(): - result = await client.run("...") # $5 USDC charged, credited to the API key's project - print(result.output) - -asyncio.run(main()) - -``` - -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; - -const client = new BrowserUse({ - apiKey: "bu_...", - x402PrivateKey: "0x...", - baseUrl: "https://x402.api.browser-use.com/api/v3", -}); -const result = await client.run("..."); -``` - -When the backend sees both a payment and a valid API key, the credit goes to the key's project rather than auto-creating a new wallet-keyed one. Useful for: - -- Agents that ran out of free-tier credits and need to keep going -- Adding credits via crypto when you already have a regular Browser Use account -- Multi-wallet setups funding one shared account - -## Checking your credit balance - -When you sign up the normal way, Browser Use creates an **account** for you (we call it a "project") that holds your credits and runs your tasks, and you log into it with an API key. When you pay with **only a wallet** (no API key), there's no signup step — so the very first time you pay, Browser Use automatically creates one of these same accounts for you and ties it to your wallet. From then on it behaves exactly like a normal account. The only difference is how you prove it's yours: instead of an API key, you sign with your wallet. - -This balance is your **Browser Use credit balance** — the prepaid USD you've added to that account through x402 payments, minus what your tasks have spent. - -To check how much credit that account has left, use the method below: - -```python Python -import asyncio - -from browser_use_sdk.v3 import get_wallet_balance - -async def main(): - balance = await get_wallet_balance("0x...") # same wallet private key you pay with - print(balance["total_credits_usd"]) - -asyncio.run(main()) - -``` - -```typescript TypeScript -import { getWalletBalance } from "browser-use-sdk/v3"; - -const balance = await getWalletBalance("0x..."); // same wallet private key you pay with -console.log(balance.total_credits_usd); -``` - -The response contains: - -| Field | Description | -| ------------------------ | ------------------------------------------------------------------------------- | -| `wallet` | The wallet address (lowercased) | -| `project_id` | The account (project) tied to your wallet that the credits live in | -| `total_credits_usd` | Your remaining Browser Use credit balance, in USD | -| `additional_credits_usd` | Of that total, the portion added via x402 top-ups (excludes any plan allowance) | - - This is for accounts created from a wallet (the default x402 mode). If you're - [topping up an existing account](#topping-up-an-existing-account), check that - account's balance the normal way with your API key via - `client.billing.account()`. A wallet that has never paid yet has no account, - so the call returns `404` until the first payment. - - The SDK signs a fixed, server-defined message - ([EIP-191](https://eips.ethereum.org/EIPS/eip-191), the same "Sign-In with - Ethereum" mechanism) with your wallet's private key. The signature proves you - control the address without moving any funds. The server recovers the signer, - matches it to the wallet's project, and returns the balance. - -## How it works - -Your code asks for something, we say "$5 please," your wallet pays automatically, we run your request. - -A bit more detail: - -1. Your code makes a request (e.g. "run this task"). -2. The SDK auto-signs the payment from your wallet and resends the request. -3. Coinbase moves the USDC on-chain. We add the same amount to your project's credit balance. -4. We run your task and send back the result. - -## Wallet setup - -If you don't have a wallet ready, here's an easy way to set one up using **MetaMask**. It's a popular crypto wallet. Any other EVM-compatible wallet works equally well: [Rabby](https://rabby.io), [Coinbase Wallet](https://www.coinbase.com/wallet), [Frame](https://frame.sh), [Trust Wallet](https://trustwallet.com), [Phantom](https://phantom.com), etc. Pick whichever you prefer. - -Get the [MetaMask browser extension](https://metamask.io) via the official -site only. Create a new wallet, save the seed phrase somewhere offline, set -a password. -By default, most wallets only show Ethereum. You need to add **Base** (the -network we accept payments on) so your wallet can hold USDC there. -Click **"Buy"** inside MetaMask. Pick **USDC**, set network to **Base**, and -pay with credit card, bank, etc. The USDC lands directly in your wallet. -In MetaMask: click the account menu → **Account details** → **Private keys** -→ enter your password → copy. That string (starts with `0x`) is your -`BROWSER_USE_X402_PRIVATE_KEY`. Other wallets have similar export options in -their account settings. - - Wallets hold real money, and anyone with the private key can drain it. Be - careful with your keys. - -## Advanced: bring your own x402 client - -For custom signers, multi-network setups, or non-EVM wallets, build the x402 client yourself, and pass it as `x402` instead of `x402_private_key`: - -```python Python -from x402 import x402Client -from x402.mechanisms.evm import EthAccountSigner -from x402.mechanisms.evm.exact.register import register_exact_evm_client -from eth_account import Account -from browser_use_sdk.v3 import AsyncBrowserUse - -x402 = x402Client() -register_exact_evm_client(x402, EthAccountSigner(Account.from_key("0x..."))) -client = AsyncBrowserUse(x402=x402) - -``` - -```typescript TypeScript -import { x402Client } from "@x402/fetch"; -import { ExactEvmScheme } from "@x402/evm"; -import { privateKeyToAccount } from "viem/accounts"; -import { BrowserUse } from "browser-use-sdk/v3"; - -const x402 = new x402Client(); -x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount("0x..."))); -const client = new BrowserUse({ x402 }); +} ``` -## Troubleshooting - -Two likely causes: - -- **Wallet has no USDC on Base.** Check your balance. If empty, top it up. -- **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks). - - - You haven't installed the optional x402 deps. Run `pip install - "browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem` - (TypeScript). - - We verified your payment request but couldn't credit your project, so we - deliberately did not settle on-chain. No USDC was moved, so just retry. This - is rare. - - Wait a few seconds. Settlement and credit grant happen in the same request, - but the response may be sent before the credit grant fully commits. If credits - still show `$0` after a few minutes, contact support with your wallet address. - (Conversely, if a payment settles but the request itself then fails, we - automatically reclaim the credits so you aren't charged for nothing.) +Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`: -`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address. +- `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`) -## Related +**3. Use it** -- [x402 protocol spec](https://www.x402.org) -- [Standard API key auth](https://docs.browser-use.com/cloud/quickstart) — alternative if you don't want pay-per-use -- [`x402` Claude Code skill source](https://github.com/browser-use/browser-use/tree/main/skills/x402) +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: -# 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 +``` + +## Option 2: Browser Use CLI -- **`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. +The [Browser Use CLI](https://docs.browser-use.com/open-source/browser-use-cli) is a standalone tool that gives Hermes browser automation through terminal commands. Hermes drives the browser directly via its terminal tool, using Browser Harness and Python helpers through the `browser-use` command. -The 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: +### Setup -```typescript page.tsx -async function handleSend(message: string) { - const session = await createSession(); +**1. Install the CLI** - router.push( - `/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}` - ); -} +```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 ``` ---- - -## Summary - -| 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 | +### 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. # Agent Sign Up for Browser Use Source: https://docs.browser-use.com/cloud/agent-signup @@ -2692,16 +1267,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](https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session). +See the [API V4 quick start](https://docs.browser-use.com/cloud/agent/quickstart). ## Claim the account @@ -2734,171 +1309,6 @@ browser-use auth status Replace `bu_...` with the key returned by the REST flow. - -# Grow Therapy provider search -Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare - - -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. - -## What you'll build - -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 - ---- - -## Setup - -```python Python -import asyncio -import json -from pydantic import BaseModel -from browser_use_sdk.v3 import AsyncBrowserUse - -client = AsyncBrowserUse() -``` -```typescript TypeScript -import { BrowserUse } from "browser-use-sdk/v3"; -import { z } from "zod"; - -const client = new BrowserUse(); -``` - -## 1. Define the output schema - -```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(), -}); -``` - -## 2. Create a workspace - -```python Python -workspace = await client.workspaces.create(name="grow-therapy-search") -``` -```typescript TypeScript -const workspace = await client.workspaces.create({ name: "grow-therapy-search" }); -``` - -## 3. Search for providers - -```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 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() -``` -```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}`); -} -``` - -## 4. Sweep across locations and specialties - -After the first run caches the search flow, sweep across different parameters at $0 LLM cost: - -```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") -``` -```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`); - } -} -``` - ---- - -## 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 | - -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. - -## Next steps - -- [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. - - # FAQ Source: https://docs.browser-use.com/cloud/faq @@ -2939,26 +1349,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 vs v4 — which should I use? +## V2 or V4 — which should I use? -**Use v4 for new agent integrations.** It is designed for long-horizon work: +Use **V4** for difficult tasks where accuracy matters. It supports: - Run-focused API with a cheap status polling endpoint -- Conversation sessions with queued and interrupting follow-ups +- 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 -V3 remains available for existing integrations and older features that have not moved to V4, including server-side structured-output schemas and automatic script caching. V2 is the legacy API closest to the open-source browser agent. - -```python -# v4 (recommended for new agent runs) -from browser_use_sdk.v4 import AsyncBrowserUse - -# v3 (existing session-based integrations) -from browser_use_sdk.v3 import AsyncBrowserUse as AsyncBrowserUseV3 -``` +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 @@ -3108,7 +1513,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 @@ -3124,7 +1528,6 @@ const share = await client.sessions.createShare(session.id); console.log(share.shareUrl); ``` - # Skills Source: https://docs.browser-use.com/cloud/legacy/skills @@ -3212,7 +1615,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 @@ -3293,7 +1695,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 @@ -3350,7 +1751,6 @@ const result = await client.run( ); ``` - # API Reference Source: https://docs.browser-use.com/cloud/api-v4-overview @@ -3407,61 +1807,6 @@ curl -X POST https://api.browser-use.com/api/v4/sessions/SESSION_ID/queue \ 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 Reference -Source: https://docs.browser-use.com/cloud/api-reference - - -## Authentication - -All requests require an API key in the `X-Browser-Use-API-Key` header: - -``` -X-Browser-Use-API-Key: bu_your_key_here -``` - -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1). Keys start with `bu_`. - -## Base URL - -``` -https://api.browser-use.com/api/v3 -``` - -## Quick example - -```bash Create a session -curl -X POST https://api.browser-use.com/api/v3/sessions \ - -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 \ - -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 -``` - ---- - -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()`. - -```bash Python -pip install browser-use-sdk -``` -```bash TypeScript -npm install browser-use-sdk -``` - - # API key Source: https://docs.browser-use.com/cloud/api-v2-overview diff --git a/docs/llms.txt b/docs/llms.txt index 05bab8ad..5952f01e 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,6 +1,6 @@ -# Browser Use Cloud SDK +# Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Use API v4 for new agent integrations. Auth via `X-Browser-Use-API-Key` header (keys start with `bu_`). +> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `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 @@ -8,7 +8,11 @@ - 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. -**Use v4 for agent runs.** V2 is legacy. Standalone browser and profile SDK resources remain in their documented namespace. +**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. 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` @@ -21,29 +25,29 @@ 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. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a high-accuracy browser task with Python, TypeScript, or curl. - [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): Run a long-horizon browser agent with one task and a few lines of code. +- [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, then validate the V4 run result in your application. -- [Follow-up tasks](https://docs.browser-use.com/cloud/agent/follow-up-tasks): Continue the same V4 conversation, workspace, and browser. -- [Live messages](https://docs.browser-use.com/cloud/agent/streaming): Poll V4 run events incrementally to monitor progress or build a custom UI. -- [Workspaces & files](https://docs.browser-use.com/cloud/agent/workspaces): Give a V4 run input files and retrieve files the agent creates. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save and test a reusable script, then run it again from the same workspace. -- [Human in the loop](https://docs.browser-use.com/cloud/agent/human-in-the-loop): Open the V4 live browser, let a person take over, then continue the same session. +- [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. +- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save, test, and reuse a script in a workspace. +- [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. +- [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. @@ -51,19 +55,13 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## 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. - [Hermes Agent](https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent): Give Hermes Agent cloud browser automation with Browser Use. -- [MCP Server](https://docs.browser-use.com/cloud/guides/mcp-server): Run browser automation tasks from your AI coding assistant. Connect to Claude, Cursor, Windsurf, or any MCP client. -- [Webhooks](https://docs.browser-use.com/cloud/guides/webhooks): Receive real-time notifications when tasks complete. Configure webhook endpoints for async task monitoring. -- [x402 (pay-per-request)](https://docs.browser-use.com/cloud/guides/x402): Pay for Browser Use Cloud with crypto (USDC on Base). ~30 seconds from wallet to first request. -- [n8n](https://docs.browser-use.com/cloud/tutorials/integrations/n8n): Use Browser Use as an HTTP node in n8n workflows. ## 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. - [Agent Sign Up for Browser Use](https://docs.browser-use.com/cloud/agent-signup): How the Browser Use agent challenge lets an AI agent create a free account and API key. -- [Grow Therapy provider search](https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare): Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns. ## Legacy (v2) - [Agent (v2)](https://docs.browser-use.com/cloud/legacy/agent): V2 agent models and file handling. @@ -75,8 +73,5 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## API v4 - [API Reference](https://docs.browser-use.com/cloud/api-v4-overview): Authenticate and start using the Browser Use API v4 — the current REST API for long-horizon agents. -## API v3 -- [API Reference](https://docs.browser-use.com/cloud/api-reference): Authenticate and start using the Browser Use REST API. - ## API v2 - [API key](https://docs.browser-use.com/cloud/api-v2-overview): Set your API key to access the Browser Use v2 REST API. From b9ca9fd5a337c04e67c9667f45dd66d25a915383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 17:48:38 -0700 Subject: [PATCH 03/15] docs: refine v4 agent concepts --- docs/cloud/agent/cache-script.mdx | 28 -- docs/cloud/agent/models.mdx | 5 +- docs/cloud/agent/quickstart.mdx | 38 +- docs/cloud/agent/scripts.mdx | 46 ++ docs/cloud/agent/sessions.mdx | 28 +- docs/cloud/agent/workspaces.mdx | 31 +- .../images/v4-agent-overview-dark.excalidraw | 324 +++++++++++++ docs/cloud/images/v4-agent-overview-dark.svg | 30 ++ ...raw => v4-agent-overview-light.excalidraw} | 226 ++++----- docs/cloud/images/v4-agent-overview-light.svg | 30 ++ docs/cloud/images/v4-agent-overview.png | Bin 114314 -> 0 bytes docs/cloud/images/v4-agent-overview.svg | 38 -- docs/cloud/images/v4-scripts-dark.excalidraw | 429 +++++++++++++++++ docs/cloud/images/v4-scripts-dark.svg | 32 ++ docs/cloud/images/v4-scripts-light.excalidraw | 429 +++++++++++++++++ docs/cloud/images/v4-scripts-light.svg | 32 ++ docs/cloud/images/v4-sessions-dark.excalidraw | 324 +++++++++++++ docs/cloud/images/v4-sessions-dark.svg | 26 ++ ...xcalidraw => v4-sessions-light.excalidraw} | 200 ++++---- docs/cloud/images/v4-sessions-light.svg | 26 ++ docs/cloud/images/v4-sessions.png | Bin 85025 -> 0 bytes docs/cloud/images/v4-sessions.svg | 33 -- .../images/v4-workspaces-dark.excalidraw | 442 ++++++++++++++++++ docs/cloud/images/v4-workspaces-dark.svg | 32 ++ ...alidraw => v4-workspaces-light.excalidraw} | 226 ++++----- docs/cloud/images/v4-workspaces-light.svg | 32 ++ docs/cloud/images/v4-workspaces.png | Bin 113738 -> 0 bytes docs/cloud/images/v4-workspaces.svg | 41 -- docs/cloud/llms-full.txt | 132 ++++-- docs/cloud/llms.txt | 2 +- docs/cloud/tutorials/grow-therapy-compare.mdx | 8 +- docs/docs.json | 6 +- docs/llms-full.txt | 132 ++++-- docs/llms.txt | 2 +- 34 files changed, 2722 insertions(+), 688 deletions(-) delete mode 100644 docs/cloud/agent/cache-script.mdx create mode 100644 docs/cloud/agent/scripts.mdx create mode 100644 docs/cloud/images/v4-agent-overview-dark.excalidraw create mode 100644 docs/cloud/images/v4-agent-overview-dark.svg rename docs/cloud/images/{v4-agent-overview.excalidraw => v4-agent-overview-light.excalidraw} (57%) create mode 100644 docs/cloud/images/v4-agent-overview-light.svg delete mode 100644 docs/cloud/images/v4-agent-overview.png delete mode 100644 docs/cloud/images/v4-agent-overview.svg create mode 100644 docs/cloud/images/v4-scripts-dark.excalidraw create mode 100644 docs/cloud/images/v4-scripts-dark.svg create mode 100644 docs/cloud/images/v4-scripts-light.excalidraw create mode 100644 docs/cloud/images/v4-scripts-light.svg create mode 100644 docs/cloud/images/v4-sessions-dark.excalidraw create mode 100644 docs/cloud/images/v4-sessions-dark.svg rename docs/cloud/images/{v4-sessions.excalidraw => v4-sessions-light.excalidraw} (61%) create mode 100644 docs/cloud/images/v4-sessions-light.svg delete mode 100644 docs/cloud/images/v4-sessions.png delete mode 100644 docs/cloud/images/v4-sessions.svg create mode 100644 docs/cloud/images/v4-workspaces-dark.excalidraw create mode 100644 docs/cloud/images/v4-workspaces-dark.svg rename docs/cloud/images/{v4-workspaces.excalidraw => v4-workspaces-light.excalidraw} (68%) create mode 100644 docs/cloud/images/v4-workspaces-light.svg delete mode 100644 docs/cloud/images/v4-workspaces.png delete mode 100644 docs/cloud/images/v4-workspaces.svg diff --git a/docs/cloud/agent/cache-script.mdx b/docs/cloud/agent/cache-script.mdx deleted file mode 100644 index d965953a..00000000 --- a/docs/cloud/agent/cache-script.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Deterministic rerun -description: "Have the agent save, test, and reuse a script in a workspace." -icon: bolt ---- - -Create one [workspace](/cloud/agent/workspaces) for the workflow, then use -these prompts with the same `workspace_id` / `workspaceId`. The [run -code](/cloud/agent/quickstart) stays exactly the same. - -## First run - -```text -Complete this task: 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 still starts an agent and uses tokens. The saved script gives the agent a -faster, more predictable path; it is not automatic zero-LLM execution. diff --git a/docs/cloud/agent/models.mdx b/docs/cloud/agent/models.mdx index 0de266b8..92f8b92e 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -19,8 +19,9 @@ Token prices are USD per 1 million tokens. Browser sessions proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.com/pricing). - **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** - when accuracy matters most. + **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. diff --git a/docs/cloud/agent/quickstart.mdx b/docs/cloud/agent/quickstart.mdx index 415fd6e7..37f41304 100644 --- a/docs/cloud/agent/quickstart.mdx +++ b/docs/cloud/agent/quickstart.mdx @@ -40,27 +40,25 @@ curl https://api.browser-use.com/api/v4/runs \ 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** and a **workspace**: +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. - - A run belongs to a conversation session and uses a persistent workspace - +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 - - - Continue the same conversation and browser. - - - Keep files across runs and sessions. - - - Poll ordered events while a run is active. + + + Give the compact context file to your coding agent. - - - Give this 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 00000000..3516dc2a --- /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 index 79c85693..5c159bc6 100644 --- a/docs/cloud/agent/sessions.mdx +++ b/docs/cloud/agent/sessions.mdx @@ -5,14 +5,21 @@ icon: comments --- A **session** holds the agent's conversation and can reuse its live browser. -Every run creates one implicitly unless you pass an existing session ID. +One session ID can contain multiple runs. Every run creates a session implicitly +unless you pass an existing session ID. - - A session containing a first run and a follow-up run - +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: @@ -43,7 +50,6 @@ console.log(result.result); ``` -- Omit the session ID for a new conversation. -- Reuse it for a follow-up with the same context and workspace. -- Pass only a [workspace ID](/cloud/agent/workspaces) for a fresh conversation - that shares files. +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/workspaces.mdx b/docs/cloud/agent/workspaces.mdx index e2c71fbc..2d193d6e 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -4,15 +4,21 @@ description: "Persist files across V4 runs and conversations." icon: folder --- -A **workspace** is a persistent filesystem. A run can read attached inputs, -create files, and share those files with later sessions. +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. - - Two independent sessions reading and writing the same workspace - +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 and attach a file @@ -44,8 +50,7 @@ const run = await client.runs.create({ ``` -Attachments are turn-scoped. Reusing a workspace does not automatically attach -every upload to later runs. +Attachments are run-scoped. Reusing a workspace does not reattach every upload. ## Retrieve created files @@ -71,6 +76,6 @@ for (const file of files.files) { ``` -Download URLs expire after 60 seconds. See [Upload workspace -files](/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace -files](/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. +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/images/v4-agent-overview-dark.excalidraw b/docs/cloud/images/v4-agent-overview-dark.excalidraw new file mode 100644 index 00000000..a22a37b9 --- /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": 70, + "y": 165, + "width": 225, + "height": 118, + "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": 100, + "y": 206, + "width": 165, + "height": 33, + "text": "TASK", + "originalText": "TASK", + "fontSize": 26, + "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": 300, + "y": 224, + "width": 91, + "height": 0, + "strokeColor": "#FE750E", + "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 + ], + [ + 91, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "session", + "x": 398, + "y": 114, + "width": 326, + "height": 220, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "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": 426, + "y": 134, + "width": 270, + "height": 30, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 26, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "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": 438, + "y": 183, + "width": 246, + "height": 78, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "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": 468, + "y": 205, + "width": 186, + "height": 33, + "text": "RUN", + "originalText": "RUN", + "fontSize": 26, + "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": 730, + "y": 224, + "width": 89, + "height": 0, + "strokeColor": "#FE750E", + "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": 826, + "y": 137, + "width": 292, + "height": 174, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "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": 858, + "y": 180, + "width": 228, + "height": 70, + "text": "WORKSPACE\nfiles", + "originalText": "WORKSPACE\nfiles", + "fontSize": 24, + "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 00000000..94dcc14f --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-dark.svg @@ -0,0 +1,30 @@ + + Task, session, run, and workspace relationship + A task starts a run inside a session. The run reads and writes persistent workspace files. + + + + + + + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + files + + diff --git a/docs/cloud/images/v4-agent-overview.excalidraw b/docs/cloud/images/v4-agent-overview-light.excalidraw similarity index 57% rename from docs/cloud/images/v4-agent-overview.excalidraw rename to docs/cloud/images/v4-agent-overview-light.excalidraw index 034017a1..f8eeaa72 100644 --- a/docs/cloud/images/v4-agent-overview.excalidraw +++ b/docs/cloud/images/v4-agent-overview-light.excalidraw @@ -3,38 +3,6 @@ "version": 2, "source": "https://excalidraw.com", "elements": [ - { - "type": "text", - "id": "title", - "x": 68, - "y": 38, - "width": 540, - "height": 38, - "text": "One task creates the context around it", - "originalText": "One task creates the context around it", - "fontSize": 30, - "fontFamily": 3, - "textAlign": "left", - "verticalAlign": "top", - "strokeColor": "#1e40af", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10101, - "version": 1, - "versionNonce": 20101, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "rectangle", "id": "task", @@ -42,12 +10,12 @@ "y": 165, "width": 225, "height": 118, - "strokeColor": "#c2410c", - "backgroundColor": "#fed7aa", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF4EC", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10102, @@ -58,27 +26,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "taskText", "x": 100, - "y": 188, + "y": 206, "width": 165, - "height": 66, - "text": "YOUR TASK\nNatural-language goal", - "originalText": "YOUR TASK\nNatural-language goal", - "fontSize": 18, + "height": 33, + "text": "TASK", + "originalText": "TASK", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10103, @@ -99,12 +69,12 @@ "y": 224, "width": 91, "height": 0, - "strokeColor": "#c2410c", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10104, @@ -115,7 +85,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [91, 0]], + "points": [ + [ + 0, + 0 + ], + [ + 91, + 0 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -128,12 +107,12 @@ "y": 114, "width": 326, "height": 220, - "strokeColor": "#6d28d9", - "backgroundColor": "#ddd6fe", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10105, @@ -144,7 +123,9 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", @@ -155,16 +136,16 @@ "height": 30, "text": "SESSION", "originalText": "SESSION", - "fontSize": 22, + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#6d28d9", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10106, @@ -185,12 +166,12 @@ "y": 183, "width": 246, "height": 78, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10107, @@ -201,27 +182,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "runText", "x": 468, - "y": 197, + "y": 205, "width": 186, - "height": 49, - "text": "RUN\nExecutes one turn", - "originalText": "RUN\nExecutes one turn", - "fontSize": 17, + "height": 33, + "text": "RUN", + "originalText": "RUN", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10108, @@ -235,38 +218,6 @@ "containerId": null, "lineHeight": 1.35 }, - { - "type": "text", - "id": "sessionDetail", - "x": 454, - "y": 284, - "width": 214, - "height": 26, - "text": "conversation + live browser", - "originalText": "conversation + live browser", - "fontSize": 15, - "fontFamily": 3, - "textAlign": "center", - "verticalAlign": "top", - "strokeColor": "#64748b", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10109, - "version": 1, - "versionNonce": 20109, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "arrow", "id": "runToWorkspace", @@ -274,12 +225,12 @@ "y": 224, "width": 89, "height": 0, - "strokeColor": "#6d28d9", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10110, @@ -290,7 +241,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [89, 0]], + "points": [ + [ + 0, + 0 + ], + [ + 89, + 0 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -303,12 +263,12 @@ "y": 137, "width": 292, "height": 174, - "strokeColor": "#047857", - "backgroundColor": "#a7f3d0", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10111, @@ -319,27 +279,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "workspaceText", "x": 858, - "y": 171, + "y": 180, "width": 228, - "height": 100, - "text": "WORKSPACE\nPersistent files\ninputs • scripts • outputs", - "originalText": "WORKSPACE\nPersistent files\ninputs • scripts • outputs", - "fontSize": 18, + "height": 70, + "text": "WORKSPACE\nfiles", + "originalText": "WORKSPACE\nfiles", + "fontSize": 24, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10112, @@ -352,38 +314,6 @@ "locked": false, "containerId": null, "lineHeight": 1.35 - }, - { - "type": "text", - "id": "footer", - "x": 272, - "y": 382, - "width": 650, - "height": 29, - "text": "Omit both IDs and API V4 creates the session and workspace automatically.", - "originalText": "Omit both IDs and API V4 creates the session and workspace automatically.", - "fontSize": 16, - "fontFamily": 3, - "textAlign": "center", - "verticalAlign": "top", - "strokeColor": "#64748b", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10113, - "version": 1, - "versionNonce": 20113, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 } ], "appState": { 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 00000000..1662f700 --- /dev/null +++ b/docs/cloud/images/v4-agent-overview-light.svg @@ -0,0 +1,30 @@ + + Task, session, run, and workspace relationship + A task starts a run inside a session. The run reads and writes persistent workspace files. + + + + + + + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + files + + diff --git a/docs/cloud/images/v4-agent-overview.png b/docs/cloud/images/v4-agent-overview.png deleted file mode 100644 index a46f347520742a25ea6bb64b2beeb1c482c17556..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114314 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

WnL7E#iNrmPNf-a&nItA7SK6P?HD;UUUP17 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 g)*k==U9qNH0&SV^XZx<#(N{Tr%EO6x; - - - - - - - - - - - One task creates the context around it - - - YOUR TASK - Natural-language goal - - - - SESSION - - RUN - Executes one turn - conversation + live browser - - - - WORKSPACE - Persistent files - inputs • scripts • outputs - - Omit both IDs and API V4 creates the session and workspace automatically. - diff --git a/docs/cloud/images/v4-scripts-dark.excalidraw b/docs/cloud/images/v4-scripts-dark.excalidraw new file mode 100644 index 00000000..8cc27484 --- /dev/null +++ b/docs/cloud/images/v4-scripts-dark.excalidraw @@ -0,0 +1,429 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "firstRun", + "x": 65, + "y": 142, + "width": 220, + "height": 116, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", + "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": 104, + "y": 183, + "width": 142, + "height": 35, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 26, + "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": 296, + "y": 200, + "width": 124, + "height": 0, + "strokeColor": "#FE750E", + "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 + ], + [ + 124, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 430, + "y": 65, + "width": 340, + "height": 270, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "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": 490, + "y": 91, + "width": 220, + "height": 35, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 26, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "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": 157, + "width": 125, + "height": 112, + "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": 487, + "y": 198, + "width": 101, + "height": 31, + "text": "script.py", + "originalText": "script.py", + "fontSize": 22, + "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": "rectangle", + "id": "readme", + "x": 615, + "y": 157, + "width": 110, + "height": 112, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10408, + "version": 1, + "versionNonce": 20408, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "readmeText", + "x": 623, + "y": 198, + "width": 94, + "height": 31, + "text": "README", + "originalText": "README", + "fontSize": 22, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10409, + "version": 1, + "versionNonce": 20409, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "reuseArrow", + "x": 780, + "y": 200, + "width": 124, + "height": 0, + "strokeColor": "#FE750E", + "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 + ], + [ + 124, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "laterRun", + "x": 915, + "y": 142, + "width": 220, + "height": 116, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "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": 954, + "y": 183, + "width": 142, + "height": 35, + "text": "RUN 2+", + "originalText": "RUN 2+", + "fontSize": 26, + "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": 1025, + "y": 270, + "width": 480, + "height": 74, + "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 + ], + [ + -90, + 74 + ], + [ + -395, + 74 + ], + [ + -480, + 10 + ] + ], + "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 00000000..3c441b17 --- /dev/null +++ b/docs/cloud/images/v4-scripts-dark.svg @@ -0,0 +1,32 @@ + + Save, reuse, and repair a browser script + The first run saves a script and README in a workspace. Later runs reuse the files and can repair the script. + + + + + + + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + script.py + README + 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 00000000..97bb2e84 --- /dev/null +++ b/docs/cloud/images/v4-scripts-light.excalidraw @@ -0,0 +1,429 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "firstRun", + "x": 65, + "y": 142, + "width": 220, + "height": 116, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF4EC", + "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": 104, + "y": 183, + "width": 142, + "height": 35, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 26, + "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": 296, + "y": 200, + "width": 124, + "height": 0, + "strokeColor": "#FE750E", + "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 + ], + [ + 124, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 430, + "y": 65, + "width": 340, + "height": 270, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", + "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": 490, + "y": 91, + "width": 220, + "height": 35, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 26, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "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": 157, + "width": 125, + "height": 112, + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", + "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": 487, + "y": 198, + "width": 101, + "height": 31, + "text": "script.py", + "originalText": "script.py", + "fontSize": 22, + "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": "rectangle", + "id": "readme", + "x": 615, + "y": 157, + "width": 110, + "height": 112, + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10408, + "version": 1, + "versionNonce": 20408, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "readmeText", + "x": 623, + "y": 198, + "width": 94, + "height": 31, + "text": "README", + "originalText": "README", + "fontSize": 22, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#18181B", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10409, + "version": 1, + "versionNonce": 20409, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "id": "reuseArrow", + "x": 780, + "y": 200, + "width": 124, + "height": 0, + "strokeColor": "#FE750E", + "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 + ], + [ + 124, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "laterRun", + "x": 915, + "y": 142, + "width": 220, + "height": 116, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", + "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": 954, + "y": 183, + "width": 142, + "height": 35, + "text": "RUN 2+", + "originalText": "RUN 2+", + "fontSize": 26, + "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": 1025, + "y": 270, + "width": 480, + "height": 74, + "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 + ], + [ + -90, + 74 + ], + [ + -395, + 74 + ], + [ + -480, + 10 + ] + ], + "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 00000000..27db3b06 --- /dev/null +++ b/docs/cloud/images/v4-scripts-light.svg @@ -0,0 +1,32 @@ + + Save, reuse, and repair a browser script + The first run saves a script and README in a workspace. Later runs reuse the files and can repair the script. + + + + + + + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + script.py + README + 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 00000000..4c22356b --- /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": 66, + "y": 112, + "width": 1048, + "height": 250, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "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": 98, + "y": 132, + "width": 220, + "height": 29, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 26, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#FE750E", + "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": 106, + "y": 194, + "width": 245, + "height": 90, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "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": 128, + "y": 221, + "width": 201, + "height": 34, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 26, + "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": 357, + "y": 239, + "width": 87, + "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 + ], + [ + 87, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run2", + "x": 450, + "y": 194, + "width": 280, + "height": 90, + "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": 472, + "y": 221, + "width": 236, + "height": 34, + "text": "RUN 2", + "originalText": "RUN 2", + "fontSize": 26, + "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": 736, + "y": 239, + "width": 87, + "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 + ], + [ + 87, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "run3", + "x": 829, + "y": 194, + "width": 245, + "height": 90, + "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": 851, + "y": 221, + "width": 201, + "height": 34, + "text": "RUN 3", + "originalText": "RUN 3", + "fontSize": 26, + "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 00000000..ce4b041d --- /dev/null +++ b/docs/cloud/images/v4-sessions-dark.svg @@ -0,0 +1,26 @@ + + 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.excalidraw b/docs/cloud/images/v4-sessions-light.excalidraw similarity index 61% rename from docs/cloud/images/v4-sessions.excalidraw rename to docs/cloud/images/v4-sessions-light.excalidraw index 8a5426de..72ac20d1 100644 --- a/docs/cloud/images/v4-sessions.excalidraw +++ b/docs/cloud/images/v4-sessions-light.excalidraw @@ -3,38 +3,6 @@ "version": 2, "source": "https://excalidraw.com", "elements": [ - { - "type": "text", - "id": "title", - "x": 65, - "y": 38, - "width": 410, - "height": 38, - "text": "A session is a conversation", - "originalText": "A session is a conversation", - "fontSize": 30, - "fontFamily": 3, - "textAlign": "left", - "verticalAlign": "top", - "strokeColor": "#1e40af", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10201, - "version": 1, - "versionNonce": 20201, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "rectangle", "id": "session", @@ -42,12 +10,12 @@ "y": 112, "width": 1048, "height": 250, - "strokeColor": "#6d28d9", - "backgroundColor": "#ddd6fe", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10202, @@ -58,27 +26,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "sessionLabel", "x": 98, "y": 132, - "width": 205, + "width": 220, "height": 29, - "text": "ONE SESSION ID", - "originalText": "ONE SESSION ID", - "fontSize": 21, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 26, "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", - "strokeColor": "#6d28d9", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10203, @@ -99,12 +69,12 @@ "y": 194, "width": 245, "height": 90, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10204, @@ -115,27 +85,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "run1Text", "x": 128, - "y": 211, + "y": 221, "width": 201, - "height": 54, - "text": "RUN 1\n“Open Hacker News”", - "originalText": "RUN 1\n“Open Hacker News”", - "fontSize": 17, + "height": 34, + "text": "RUN 1", + "originalText": "RUN 1", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10205, @@ -156,12 +128,12 @@ "y": 239, "width": 87, "height": 0, - "strokeColor": "#1e3a5f", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10206, @@ -172,7 +144,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [87, 0]], + "points": [ + [ + 0, + 0 + ], + [ + 87, + 0 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -185,12 +166,12 @@ "y": 194, "width": 280, "height": 90, - "strokeColor": "#1e3a5f", - "backgroundColor": "#60a5fa", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10207, @@ -201,27 +182,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "run2Text", "x": 472, - "y": 211, + "y": 221, "width": 236, - "height": 54, - "text": "RUN 2\n“Summarize the top story”", - "originalText": "RUN 2\n“Summarize the top story”", - "fontSize": 17, + "height": 34, + "text": "RUN 2", + "originalText": "RUN 2", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10208, @@ -242,12 +225,12 @@ "y": 239, "width": 87, "height": 0, - "strokeColor": "#1e3a5f", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10209, @@ -258,7 +241,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [87, 0]], + "points": [ + [ + 0, + 0 + ], + [ + 87, + 0 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -271,12 +263,12 @@ "y": 194, "width": 245, "height": 90, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10210, @@ -287,27 +279,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "run3Text", "x": 851, - "y": 211, + "y": 221, "width": 201, - "height": 54, - "text": "RUN 3\nAnother follow-up", - "originalText": "RUN 3\nAnother follow-up", - "fontSize": 17, + "height": 34, + "text": "RUN 3", + "originalText": "RUN 3", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10211, @@ -320,38 +314,6 @@ "locked": false, "containerId": null, "lineHeight": 1.35 - }, - { - "type": "text", - "id": "footer", - "x": 236, - "y": 395, - "width": 708, - "height": 29, - "text": "Conversation, workspace, and the live browser are carried into each follow-up.", - "originalText": "Conversation, workspace, and the live browser are carried into each follow-up.", - "fontSize": 16, - "fontFamily": 3, - "textAlign": "center", - "verticalAlign": "top", - "strokeColor": "#64748b", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10212, - "version": 1, - "versionNonce": 20212, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 } ], "appState": { diff --git a/docs/cloud/images/v4-sessions-light.svg b/docs/cloud/images/v4-sessions-light.svg new file mode 100644 index 00000000..2ef0fd8e --- /dev/null +++ b/docs/cloud/images/v4-sessions-light.svg @@ -0,0 +1,26 @@ + + 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.png b/docs/cloud/images/v4-sessions.png deleted file mode 100644 index 5b0a728d2110819f82289324bce825627aa70be6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85025 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 DAw - - - - - - - - A session is a conversation - - ONE SESSION ID - - - RUN 1 - “Open Hacker News” - - - - RUN 2 - “Summarize the top story” - - - - RUN 3 - Another follow-up - - Conversation, workspace, and the live browser carry into each follow-up. - diff --git a/docs/cloud/images/v4-workspaces-dark.excalidraw b/docs/cloud/images/v4-workspaces-dark.excalidraw new file mode 100644 index 00000000..cd366d9f --- /dev/null +++ b/docs/cloud/images/v4-workspaces-dark.excalidraw @@ -0,0 +1,442 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "id": "session1", + "x": 74, + "y": 132, + "width": 270, + "height": 112, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "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": 108, + "y": 170, + "width": 202, + "height": 34, + "text": "SESSION A", + "originalText": "SESSION A", + "fontSize": 26, + "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": 74, + "y": 300, + "width": 270, + "height": 112, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "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": 108, + "y": 338, + "width": 202, + "height": 34, + "text": "SESSION B", + "originalText": "SESSION B", + "fontSize": 26, + "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": 350, + "y": 188, + "width": 165, + "height": 78, + "strokeColor": "#FE750E", + "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 + ], + [ + 165, + 78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow2", + "x": 350, + "y": 356, + "width": 165, + "height": -78, + "strokeColor": "#A1A1AA", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10307, + "version": 1, + "versionNonce": 20307, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 165, + -78 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "id": "workspace", + "x": 522, + "y": 122, + "width": 584, + "height": 300, + "strokeColor": "#FE750E", + "backgroundColor": "#1D1714", + "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": 566, + "y": 145, + "width": 496, + "height": 30, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 26, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#FE750E", + "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": 577, + "y": 211, + "width": 142, + "height": 95, + "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": 596, + "y": 240, + "width": 104, + "height": 39, + "text": "people.csv", + "originalText": "people.csv", + "fontSize": 21, + "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 + }, + { + "type": "rectangle", + "id": "file2", + "x": 743, + "y": 211, + "width": 142, + "height": 95, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10312, + "version": 1, + "versionNonce": 20312, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "file2Text", + "x": 762, + "y": 240, + "width": 104, + "height": 39, + "text": "script.py", + "originalText": "script.py", + "fontSize": 21, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10313, + "version": 1, + "versionNonce": 20313, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "file3", + "x": 909, + "y": 211, + "width": 142, + "height": 95, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10314, + "version": 1, + "versionNonce": 20314, + "isDeleted": false, + "groupIds": [], + "boundElements": null, + "link": null, + "locked": false, + "roundness": { + "type": 3 + } + }, + { + "type": "text", + "id": "file3Text", + "x": 928, + "y": 240, + "width": 104, + "height": 39, + "text": "output.json", + "originalText": "output.json", + "fontSize": 21, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#F4F4F5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "seed": 10315, + "version": 1, + "versionNonce": 20315, + "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 00000000..3b7876f4 --- /dev/null +++ b/docs/cloud/images/v4-workspaces-dark.svg @@ -0,0 +1,32 @@ + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. + + + + + + + + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + people.csv + script.py + output.json + + diff --git a/docs/cloud/images/v4-workspaces.excalidraw b/docs/cloud/images/v4-workspaces-light.excalidraw similarity index 68% rename from docs/cloud/images/v4-workspaces.excalidraw rename to docs/cloud/images/v4-workspaces-light.excalidraw index 0afe20bf..217dc485 100644 --- a/docs/cloud/images/v4-workspaces.excalidraw +++ b/docs/cloud/images/v4-workspaces-light.excalidraw @@ -3,38 +3,6 @@ "version": 2, "source": "https://excalidraw.com", "elements": [ - { - "type": "text", - "id": "title", - "x": 65, - "y": 38, - "width": 505, - "height": 38, - "text": "A workspace persists beyond a session", - "originalText": "A workspace persists beyond a session", - "fontSize": 30, - "fontFamily": 3, - "textAlign": "left", - "verticalAlign": "top", - "strokeColor": "#1e40af", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10301, - "version": 1, - "versionNonce": 20301, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "rectangle", "id": "session1", @@ -42,12 +10,12 @@ "y": 132, "width": 270, "height": 112, - "strokeColor": "#6d28d9", - "backgroundColor": "#ddd6fe", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10302, @@ -58,27 +26,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "session1Text", "x": 108, - "y": 155, + "y": 170, "width": 202, - "height": 62, - "text": "SESSION A\nUpload + create files", - "originalText": "SESSION A\nUpload + create files", - "fontSize": 17, + "height": 34, + "text": "SESSION A", + "originalText": "SESSION A", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10303, @@ -99,12 +69,12 @@ "y": 300, "width": 270, "height": 112, - "strokeColor": "#1e40af", - "backgroundColor": "#dbeafe", + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10304, @@ -115,27 +85,29 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", "id": "session2Text", "x": 108, - "y": 323, + "y": 338, "width": 202, - "height": 62, - "text": "SESSION B\nFresh session, same files", - "originalText": "SESSION B\nFresh session, same files", - "fontSize": 17, + "height": 34, + "text": "SESSION B", + "originalText": "SESSION B", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10305, @@ -156,12 +128,12 @@ "y": 188, "width": 165, "height": 78, - "strokeColor": "#6d28d9", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10306, @@ -172,7 +144,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [165, 78]], + "points": [ + [ + 0, + 0 + ], + [ + 165, + 78 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -185,12 +166,12 @@ "y": 356, "width": 165, "height": -78, - "strokeColor": "#1e40af", + "strokeColor": "#71717A", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10307, @@ -201,7 +182,16 @@ "boundElements": null, "link": null, "locked": false, - "points": [[0, 0], [165, -78]], + "points": [ + [ + 0, + 0 + ], + [ + 165, + -78 + ] + ], "startBinding": null, "endBinding": null, "startArrowhead": null, @@ -214,12 +204,12 @@ "y": 122, "width": 584, "height": 300, - "strokeColor": "#047857", - "backgroundColor": "#a7f3d0", + "strokeColor": "#FE750E", + "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10308, @@ -230,7 +220,9 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", @@ -239,18 +231,18 @@ "y": 145, "width": 496, "height": 30, - "text": "PERSISTENT WORKSPACE", - "originalText": "PERSISTENT WORKSPACE", - "fontSize": 22, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 26, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#047857", + "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10309, @@ -271,12 +263,12 @@ "y": 211, "width": 142, "height": 95, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10310, @@ -287,7 +279,9 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", @@ -298,16 +292,16 @@ "height": 39, "text": "people.csv", "originalText": "people.csv", - "fontSize": 16, + "fontSize": 21, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10311, @@ -328,12 +322,12 @@ "y": 211, "width": 142, "height": 95, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10312, @@ -344,7 +338,9 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", @@ -355,16 +351,16 @@ "height": 39, "text": "script.py", "originalText": "script.py", - "fontSize": 16, + "fontSize": 21, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10313, @@ -385,12 +381,12 @@ "y": 211, "width": 142, "height": 95, - "strokeColor": "#1e3a5f", - "backgroundColor": "#93c5fd", + "strokeColor": "#52525B", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10314, @@ -401,7 +397,9 @@ "boundElements": null, "link": null, "locked": false, - "roundness": {"type": 3} + "roundness": { + "type": 3 + } }, { "type": "text", @@ -412,16 +410,16 @@ "height": 39, "text": "output.json", "originalText": "output.json", - "fontSize": 16, + "fontSize": 21, "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#374151", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 0, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10315, @@ -434,38 +432,6 @@ "locked": false, "containerId": null, "lineHeight": 1.25 - }, - { - "type": "text", - "id": "workspaceDetail", - "x": 628, - "y": 347, - "width": 372, - "height": 29, - "text": "Reuse with workspace_id / workspaceId", - "originalText": "Reuse with workspace_id / workspaceId", - "fontSize": 16, - "fontFamily": 3, - "textAlign": "center", - "verticalAlign": "top", - "strokeColor": "#64748b", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 0, - "opacity": 100, - "angle": 0, - "seed": 10316, - "version": 1, - "versionNonce": 20316, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 } ], "appState": { diff --git a/docs/cloud/images/v4-workspaces-light.svg b/docs/cloud/images/v4-workspaces-light.svg new file mode 100644 index 00000000..277f5012 --- /dev/null +++ b/docs/cloud/images/v4-workspaces-light.svg @@ -0,0 +1,32 @@ + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. + + + + + + + + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + people.csv + script.py + output.json + + diff --git a/docs/cloud/images/v4-workspaces.png b/docs/cloud/images/v4-workspaces.png deleted file mode 100644 index 47fbb08cebe9c95a2b4da69d6252125e4394b486..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113738 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 HXKwr(_df6a diff --git a/docs/cloud/images/v4-workspaces.svg b/docs/cloud/images/v4-workspaces.svg deleted file mode 100644 index 07f63d2c..00000000 --- a/docs/cloud/images/v4-workspaces.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - A workspace persists beyond a session - - - SESSION A - Upload + create files - - - SESSION B - Fresh session, same files - - - - - - PERSISTENT WORKSPACE - - - people.csv - - script.py - - output.json - - Reuse with workspace_id / workspaceId - diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 13b4251e..e360d6c3 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -100,20 +100,24 @@ curl https://api.browser-use.com/api/v4/runs \ 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** and a **workspace**: - - - - - -Continue the same conversation and browser. -Keep files across runs and sessions. -Poll ordered events while a run is active. - - Give this compact context file to your coding agent. +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. + +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 @@ -133,8 +137,9 @@ 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). - **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** - when accuracy matters most. + **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. ```python Python run = client.runs.create( @@ -205,14 +210,21 @@ Source: https://docs.browser-use.com/cloud/agent/sessions A **session** holds the agent's conversation and can reuse its live browser. -Every run creates one implicitly unless you pass an existing session ID. - - - - +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: @@ -241,24 +253,29 @@ const result = await client.runs.waitForCompletion(followUp.id); console.log(result.result); ``` -- Omit the session ID for a new conversation. -- Reuse it for a follow-up with the same context and workspace. -- Pass only a [workspace ID](https://docs.browser-use.com/cloud/agent/workspaces) for a fresh conversation - that shares files. +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 -A **workspace** is a persistent filesystem. A run can read attached inputs, -create files, and share those files with later sessions. +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. - - - +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 and attach a file @@ -288,8 +305,7 @@ const run = await client.runs.create({ }); ``` -Attachments are turn-scoped. Reusing a workspace does not automatically attach -every upload to later runs. +Attachments are run-scoped. Reusing a workspace does not reattach every upload. ## Retrieve created files @@ -313,22 +329,40 @@ for (const file of files.files) { } ``` -Download URLs expire after 60 seconds. See [Upload workspace -files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace -files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. +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. + +# Scripts +Source: https://docs.browser-use.com/cloud/agent/scripts + -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script +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. +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](https://docs.browser-use.com/cloud/agent/workspaces) for the workflow, then use -these prompts with the same `workspace_id` / `workspaceId`. The [run -code](https://docs.browser-use.com/cloud/agent/quickstart) stays exactly the same. +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. ## First run ```text -Complete this task: get the top five Hacker News stories as JSON. +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. @@ -341,8 +375,8 @@ 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 still starts an agent and uses tokens. The saved script gives the agent a -faster, more predictable path; it is not automatic zero-LLM execution. +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. # Human in the loop Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 5952f01e..2812246c 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -34,7 +34,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [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. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save, test, and reuse a script in a workspace. +- [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. diff --git a/docs/cloud/tutorials/grow-therapy-compare.mdx b/docs/cloud/tutorials/grow-therapy-compare.mdx index 2138e6f3..938637a7 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/docs.json b/docs/docs.json index 4429e413..05e0130b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -82,7 +82,7 @@ "cloud/agent/structured-output", "cloud/agent/sessions", "cloud/agent/workspaces", - "cloud/agent/cache-script", + "cloud/agent/scripts", "cloud/agent/human-in-the-loop", "cloud/agent/observability" ] @@ -998,6 +998,10 @@ "source": "/cloud/agent/streaming", "destination": "/cloud/agent/observability" }, + { + "source": "/cloud/agent/cache-script", + "destination": "/cloud/agent/scripts" + }, { "source": "/tips/integrations/playwright", "destination": "/cloud/browser/playwright-puppeteer-selenium" diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 13b4251e..e360d6c3 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -100,20 +100,24 @@ curl https://api.browser-use.com/api/v4/runs \ 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** and a **workspace**: - - - - - -Continue the same conversation and browser. -Keep files across runs and sessions. -Poll ordered events while a run is active. - - Give this compact context file to your coding agent. +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. + +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 @@ -133,8 +137,9 @@ 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). - **MiniMax M3** is the default and cheapest option. Use **Claude Opus 5** - when accuracy matters most. + **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. ```python Python run = client.runs.create( @@ -205,14 +210,21 @@ Source: https://docs.browser-use.com/cloud/agent/sessions A **session** holds the agent's conversation and can reuse its live browser. -Every run creates one implicitly unless you pass an existing session ID. - - - - +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: @@ -241,24 +253,29 @@ const result = await client.runs.waitForCompletion(followUp.id); console.log(result.result); ``` -- Omit the session ID for a new conversation. -- Reuse it for a follow-up with the same context and workspace. -- Pass only a [workspace ID](https://docs.browser-use.com/cloud/agent/workspaces) for a fresh conversation - that shares files. +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 -A **workspace** is a persistent filesystem. A run can read attached inputs, -create files, and share those files with later sessions. +A **workspace** is a persistent filesystem shared by runs—even runs in +different sessions. Use it for inputs, scripts, and generated files. - - - +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 and attach a file @@ -288,8 +305,7 @@ const run = await client.runs.create({ }); ``` -Attachments are turn-scoped. Reusing a workspace does not automatically attach -every upload to later runs. +Attachments are run-scoped. Reusing a workspace does not reattach every upload. ## Retrieve created files @@ -313,22 +329,40 @@ for (const file of files.files) { } ``` -Download URLs expire after 60 seconds. See [Upload workspace -files](https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files) and [List workspace -files](https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files) for limits and pagination. +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. + +# Scripts +Source: https://docs.browser-use.com/cloud/agent/scripts + -# Deterministic rerun -Source: https://docs.browser-use.com/cloud/agent/cache-script +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. +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](https://docs.browser-use.com/cloud/agent/workspaces) for the workflow, then use -these prompts with the same `workspace_id` / `workspaceId`. The [run -code](https://docs.browser-use.com/cloud/agent/quickstart) stays exactly the same. +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. ## First run ```text -Complete this task: get the top five Hacker News stories as JSON. +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. @@ -341,8 +375,8 @@ 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 still starts an agent and uses tokens. The saved script gives the agent a -faster, more predictable path; it is not automatic zero-LLM execution. +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. # Human in the loop Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop diff --git a/docs/llms.txt b/docs/llms.txt index 5952f01e..2812246c 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -34,7 +34,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [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. -- [Deterministic rerun](https://docs.browser-use.com/cloud/agent/cache-script): Have the agent save, test, and reuse a script in a workspace. +- [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. From 91c08e404550f3deab93155de7a6aedf1e4c5724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:10:16 -0700 Subject: [PATCH 04/15] docs: polish v4 diagrams and browser lifecycle --- .../browser/playwright-puppeteer-selenium.mdx | 117 ++++++++++++------ .../images/v4-agent-overview-dark.excalidraw | 60 ++++----- docs/cloud/images/v4-agent-overview-dark.svg | 60 +++++---- .../images/v4-agent-overview-light.excalidraw | 60 ++++----- docs/cloud/images/v4-agent-overview-light.svg | 60 +++++---- docs/cloud/images/v4-scripts-dark.excalidraw | 68 +++++----- docs/cloud/images/v4-scripts-dark.svg | 68 +++++----- docs/cloud/images/v4-scripts-light.excalidraw | 68 +++++----- docs/cloud/images/v4-scripts-light.svg | 68 +++++----- docs/cloud/images/v4-sessions-dark.excalidraw | 64 +++++----- docs/cloud/images/v4-sessions-dark.svg | 52 ++++---- .../cloud/images/v4-sessions-light.excalidraw | 64 +++++----- docs/cloud/images/v4-sessions-light.svg | 52 ++++---- .../images/v4-workspaces-dark.excalidraw | 80 ++++++------ docs/cloud/images/v4-workspaces-dark.svg | 72 ++++++----- .../images/v4-workspaces-light.excalidraw | 80 ++++++------ docs/cloud/images/v4-workspaces-light.svg | 72 ++++++----- docs/cloud/llms-full.txt | 113 +++++++++++------ docs/cloud/llms.txt | 6 + docs/generate-llms-txt.sh | 6 + docs/llms-full.txt | 113 +++++++++++------ docs/llms.txt | 6 + 22 files changed, 816 insertions(+), 593 deletions(-) diff --git a/docs/cloud/browser/playwright-puppeteer-selenium.mdx b/docs/cloud/browser/playwright-puppeteer-selenium.mdx index a9718b35..28640013 100644 --- a/docs/cloud/browser/playwright-puppeteer-selenium.mdx +++ b/docs/cloud/browser/playwright-puppeteer-selenium.mdx @@ -4,44 +4,90 @@ 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. This page is for direct browser control. To give an AI agent a goal instead, [create an API V4 run](/cloud/agent/quickstart). -## WebSocket URL +## Create, connect, and stop -Connect with a single URL. All configuration is passed as query parameters. +Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it +with the browser session ID. ### Playwright ```python Python +import os +import requests from playwright.sync_api import sync_playwright -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(WSS_URL) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) - browser.close() -# Browser is automatically stopped when the WebSocket disconnects +api_key = os.environ["BROWSER_USE_API_KEY"] +headers = {"X-Browser-Use-API-Key": api_key} +session = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +try: + with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(session["cdpUrl"]) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) +finally: + requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{session['id']}", + headers=headers, + json={"action": "stop"}, + ).raise_for_status() ``` ```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 +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const session = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +try { + const browser = await chromium.connectOverCDP(session.cdpUrl); + const page = browser.contexts()[0].pages()[0]; + await page.goto("https://example.com"); + console.log(await page.title()); +} finally { + await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), + }); +} +``` +```bash curl +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) + +# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: +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"}' ``` @@ -50,13 +96,15 @@ 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 }); +// Create `session` with API V4 as shown above. +const browser = await puppeteer.connect({ + browserWSEndpoint: session.cdpUrl, +}); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); -await browser.close(); + +// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -64,18 +112,13 @@ await browser.close(); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. -## 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. | - - Close the CDP connection when done. Browsers left running continue to incur - charges until their timeout expires. + `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 + stop operation. Keep the returned browser session ID and call `PATCH + /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and + refunds unused browser time. + +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/images/v4-agent-overview-dark.excalidraw b/docs/cloud/images/v4-agent-overview-dark.excalidraw index a22a37b9..42b4d542 100644 --- a/docs/cloud/images/v4-agent-overview-dark.excalidraw +++ b/docs/cloud/images/v4-agent-overview-dark.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10102, @@ -37,18 +37,18 @@ "y": 206, "width": 165, "height": 33, - "text": "TASK", - "originalText": "TASK", + "text": "Task", + "originalText": "Task", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10103, @@ -72,9 +72,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10104, @@ -110,9 +110,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10105, @@ -134,18 +134,18 @@ "y": 134, "width": 270, "height": 30, - "text": "SESSION", - "originalText": "SESSION", + "text": "Session", + "originalText": "Session", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10106, @@ -169,9 +169,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10107, @@ -193,18 +193,18 @@ "y": 205, "width": 186, "height": 33, - "text": "RUN", - "originalText": "RUN", + "text": "Run", + "originalText": "Run", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10108, @@ -228,9 +228,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10110, @@ -266,9 +266,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10111, @@ -293,15 +293,15 @@ "text": "WORKSPACE\nfiles", "originalText": "WORKSPACE\nfiles", "fontSize": 24, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10112, diff --git a/docs/cloud/images/v4-agent-overview-dark.svg b/docs/cloud/images/v4-agent-overview-dark.svg index 94dcc14f..16828e12 100644 --- a/docs/cloud/images/v4-agent-overview-dark.svg +++ b/docs/cloud/images/v4-agent-overview-dark.svg @@ -1,30 +1,38 @@ - + Task, session, run, and workspace relationship - A task starts a run inside a session. The run reads and writes persistent workspace files. + A task starts a run inside a session. The run reads and writes files in a persistent workspace. - - - - - + + + - - - - - - - - - - - - - - TASK - SESSION - RUN - WORKSPACE - files - + + + + + Task + + + + + Session + + session_id + + + + + Run + conversation + live browser + + + + + Workspace + + + files + + + scripts diff --git a/docs/cloud/images/v4-agent-overview-light.excalidraw b/docs/cloud/images/v4-agent-overview-light.excalidraw index f8eeaa72..1a1268fe 100644 --- a/docs/cloud/images/v4-agent-overview-light.excalidraw +++ b/docs/cloud/images/v4-agent-overview-light.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF4EC", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10102, @@ -37,18 +37,18 @@ "y": 206, "width": 165, "height": 33, - "text": "TASK", - "originalText": "TASK", + "text": "Task", + "originalText": "Task", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10103, @@ -72,9 +72,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10104, @@ -110,9 +110,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10105, @@ -134,18 +134,18 @@ "y": 134, "width": 270, "height": 30, - "text": "SESSION", - "originalText": "SESSION", + "text": "Session", + "originalText": "Session", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10106, @@ -169,9 +169,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10107, @@ -193,18 +193,18 @@ "y": 205, "width": 186, "height": 33, - "text": "RUN", - "originalText": "RUN", + "text": "Run", + "originalText": "Run", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10108, @@ -228,9 +228,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10110, @@ -266,9 +266,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10111, @@ -293,15 +293,15 @@ "text": "WORKSPACE\nfiles", "originalText": "WORKSPACE\nfiles", "fontSize": 24, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10112, diff --git a/docs/cloud/images/v4-agent-overview-light.svg b/docs/cloud/images/v4-agent-overview-light.svg index 1662f700..5e904f29 100644 --- a/docs/cloud/images/v4-agent-overview-light.svg +++ b/docs/cloud/images/v4-agent-overview-light.svg @@ -1,30 +1,38 @@ - + Task, session, run, and workspace relationship - A task starts a run inside a session. The run reads and writes persistent workspace files. + A task starts a run inside a session. The run reads and writes files in a persistent workspace. - - - - - + + + - - - - - - - - - - - - - - TASK - SESSION - RUN - WORKSPACE - files - + + + + + Task + + + + + Session + + session_id + + + + + Run + conversation + live browser + + + + + Workspace + + + files + + + scripts diff --git a/docs/cloud/images/v4-scripts-dark.excalidraw b/docs/cloud/images/v4-scripts-dark.excalidraw index 8cc27484..45b448cd 100644 --- a/docs/cloud/images/v4-scripts-dark.excalidraw +++ b/docs/cloud/images/v4-scripts-dark.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10401, @@ -37,18 +37,18 @@ "y": 183, "width": 142, "height": 35, - "text": "RUN 1", - "originalText": "RUN 1", + "text": "Run 01", + "originalText": "Run 01", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10402, @@ -72,9 +72,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10403, @@ -110,9 +110,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10404, @@ -134,18 +134,18 @@ "y": 91, "width": 220, "height": 35, - "text": "WORKSPACE", - "originalText": "WORKSPACE", + "text": "Workspace", + "originalText": "Workspace", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10405, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10406, @@ -196,15 +196,15 @@ "text": "script.py", "originalText": "script.py", "fontSize": 22, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10407, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10408, @@ -252,18 +252,18 @@ "y": 198, "width": 94, "height": 31, - "text": "README", - "originalText": "README", + "text": "README.md", + "originalText": "README.md", "fontSize": 22, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10409, @@ -287,9 +287,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10410, @@ -325,9 +325,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10411, @@ -352,15 +352,15 @@ "text": "RUN 2+", "originalText": "RUN 2+", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10412, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10413, diff --git a/docs/cloud/images/v4-scripts-dark.svg b/docs/cloud/images/v4-scripts-dark.svg index 3c441b17..246102b3 100644 --- a/docs/cloud/images/v4-scripts-dark.svg +++ b/docs/cloud/images/v4-scripts-dark.svg @@ -1,32 +1,42 @@ - - Save, reuse, and repair a browser script - The first run saves a script and README in a workspace. Later runs reuse the files and can repair the script. + + Save and reuse tested scripts + A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. - - - - + + + + + + - - - - - - - - - - - - - - - - - RUN 1 - WORKSPACE - script.py - README - RUN 2+ - + + + + + + Run 01 + create · test · document + + + + + Workspace + + + script.py + + + README.md + + + + + + + Run 02+ + reuse first + + + + repair only if needed diff --git a/docs/cloud/images/v4-scripts-light.excalidraw b/docs/cloud/images/v4-scripts-light.excalidraw index 97bb2e84..a287c25f 100644 --- a/docs/cloud/images/v4-scripts-light.excalidraw +++ b/docs/cloud/images/v4-scripts-light.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF4EC", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10401, @@ -37,18 +37,18 @@ "y": 183, "width": 142, "height": 35, - "text": "RUN 1", - "originalText": "RUN 1", + "text": "Run 01", + "originalText": "Run 01", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10402, @@ -72,9 +72,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10403, @@ -110,9 +110,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10404, @@ -134,18 +134,18 @@ "y": 91, "width": 220, "height": 35, - "text": "WORKSPACE", - "originalText": "WORKSPACE", + "text": "Workspace", + "originalText": "Workspace", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10405, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10406, @@ -196,15 +196,15 @@ "text": "script.py", "originalText": "script.py", "fontSize": 22, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10407, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10408, @@ -252,18 +252,18 @@ "y": 198, "width": 94, "height": 31, - "text": "README", - "originalText": "README", + "text": "README.md", + "originalText": "README.md", "fontSize": 22, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10409, @@ -287,9 +287,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10410, @@ -325,9 +325,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10411, @@ -352,15 +352,15 @@ "text": "RUN 2+", "originalText": "RUN 2+", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10412, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10413, diff --git a/docs/cloud/images/v4-scripts-light.svg b/docs/cloud/images/v4-scripts-light.svg index 27db3b06..0d1454f9 100644 --- a/docs/cloud/images/v4-scripts-light.svg +++ b/docs/cloud/images/v4-scripts-light.svg @@ -1,32 +1,42 @@ - - Save, reuse, and repair a browser script - The first run saves a script and README in a workspace. Later runs reuse the files and can repair the script. + + Save and reuse tested scripts + A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. - - - - + + + + + + - - - - - - - - - - - - - - - - - RUN 1 - WORKSPACE - script.py - README - RUN 2+ - + + + + + + Run 01 + create · test · document + + + + + Workspace + + + script.py + + + README.md + + + + + + + Run 02+ + reuse first + + + + repair only if needed diff --git a/docs/cloud/images/v4-sessions-dark.excalidraw b/docs/cloud/images/v4-sessions-dark.excalidraw index 4c22356b..13729dd3 100644 --- a/docs/cloud/images/v4-sessions-dark.excalidraw +++ b/docs/cloud/images/v4-sessions-dark.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10202, @@ -37,18 +37,18 @@ "y": 132, "width": 220, "height": 29, - "text": "SESSION ID", - "originalText": "SESSION ID", + "text": "Session ID", + "originalText": "Session ID", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10203, @@ -72,9 +72,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10204, @@ -96,18 +96,18 @@ "y": 221, "width": 201, "height": 34, - "text": "RUN 1", - "originalText": "RUN 1", + "text": "Run 01", + "originalText": "Run 01", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10205, @@ -131,9 +131,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10206, @@ -169,9 +169,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10207, @@ -193,18 +193,18 @@ "y": 221, "width": 236, "height": 34, - "text": "RUN 2", - "originalText": "RUN 2", + "text": "Run 02", + "originalText": "Run 02", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10208, @@ -228,9 +228,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10209, @@ -266,9 +266,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10210, @@ -290,18 +290,18 @@ "y": 221, "width": 201, "height": 34, - "text": "RUN 3", - "originalText": "RUN 3", + "text": "Run 03", + "originalText": "Run 03", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10211, diff --git a/docs/cloud/images/v4-sessions-dark.svg b/docs/cloud/images/v4-sessions-dark.svg index ce4b041d..48956d22 100644 --- a/docs/cloud/images/v4-sessions-dark.svg +++ b/docs/cloud/images/v4-sessions-dark.svg @@ -1,26 +1,32 @@ - - One session with multiple runs - One session ID contains three sequential runs. + + Multiple runs in one session + Three sequential runs share one session ID, conversation, workspace, and live browser. - - - + + + - - - - - - - - - - - - - SESSION ID - RUN 1 - RUN 2 - RUN 3 - + + + Session + + ses_8f21… + + + + + Run 01 + Open Hacker News + + + + Run 02 + Summarize the story + + + + Run 03 + Continue the task + + same conversation · same workspace · same live browser diff --git a/docs/cloud/images/v4-sessions-light.excalidraw b/docs/cloud/images/v4-sessions-light.excalidraw index 72ac20d1..9b204a54 100644 --- a/docs/cloud/images/v4-sessions-light.excalidraw +++ b/docs/cloud/images/v4-sessions-light.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10202, @@ -37,18 +37,18 @@ "y": 132, "width": 220, "height": 29, - "text": "SESSION ID", - "originalText": "SESSION ID", + "text": "Session ID", + "originalText": "Session ID", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "left", "verticalAlign": "top", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10203, @@ -72,9 +72,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10204, @@ -96,18 +96,18 @@ "y": 221, "width": 201, "height": 34, - "text": "RUN 1", - "originalText": "RUN 1", + "text": "Run 01", + "originalText": "Run 01", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10205, @@ -131,9 +131,9 @@ "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10206, @@ -169,9 +169,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10207, @@ -193,18 +193,18 @@ "y": 221, "width": 236, "height": 34, - "text": "RUN 2", - "originalText": "RUN 2", + "text": "Run 02", + "originalText": "Run 02", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10208, @@ -228,9 +228,9 @@ "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10209, @@ -266,9 +266,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10210, @@ -290,18 +290,18 @@ "y": 221, "width": 201, "height": 34, - "text": "RUN 3", - "originalText": "RUN 3", + "text": "Run 03", + "originalText": "Run 03", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10211, diff --git a/docs/cloud/images/v4-sessions-light.svg b/docs/cloud/images/v4-sessions-light.svg index 2ef0fd8e..48eeb532 100644 --- a/docs/cloud/images/v4-sessions-light.svg +++ b/docs/cloud/images/v4-sessions-light.svg @@ -1,26 +1,32 @@ - - One session with multiple runs - One session ID contains three sequential runs. + + Multiple runs in one session + Three sequential runs share one session ID, conversation, workspace, and live browser. - - - + + + - - - - - - - - - - - - - SESSION ID - RUN 1 - RUN 2 - RUN 3 - + + + Session + + ses_8f21… + + + + + Run 01 + Open Hacker News + + + + Run 02 + Summarize the story + + + + Run 03 + Continue the task + + same conversation · same workspace · same live browser diff --git a/docs/cloud/images/v4-workspaces-dark.excalidraw b/docs/cloud/images/v4-workspaces-dark.excalidraw index cd366d9f..04d95d59 100644 --- a/docs/cloud/images/v4-workspaces-dark.excalidraw +++ b/docs/cloud/images/v4-workspaces-dark.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10302, @@ -37,18 +37,18 @@ "y": 170, "width": 202, "height": 34, - "text": "SESSION A", - "originalText": "SESSION A", + "text": "Session A", + "originalText": "Session A", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10303, @@ -72,9 +72,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10304, @@ -96,18 +96,18 @@ "y": 338, "width": 202, "height": 34, - "text": "SESSION B", - "originalText": "SESSION B", + "text": "Session B", + "originalText": "Session B", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10305, @@ -131,9 +131,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10306, @@ -169,9 +169,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10307, @@ -207,9 +207,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#1D1714", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10308, @@ -231,18 +231,18 @@ "y": 145, "width": 496, "height": 30, - "text": "WORKSPACE", - "originalText": "WORKSPACE", + "text": "Workspace", + "originalText": "Workspace", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10309, @@ -266,9 +266,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10310, @@ -293,15 +293,15 @@ "text": "people.csv", "originalText": "people.csv", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10311, @@ -325,9 +325,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10312, @@ -352,15 +352,15 @@ "text": "script.py", "originalText": "script.py", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10313, @@ -384,9 +384,9 @@ "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10314, @@ -411,15 +411,15 @@ "text": "output.json", "originalText": "output.json", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10315, diff --git a/docs/cloud/images/v4-workspaces-dark.svg b/docs/cloud/images/v4-workspaces-dark.svg index 3b7876f4..9f69525c 100644 --- a/docs/cloud/images/v4-workspaces-dark.svg +++ b/docs/cloud/images/v4-workspaces-dark.svg @@ -1,32 +1,46 @@ - - Sessions sharing a persistent workspace - Two independent sessions read and write files in one workspace. + + One workspace shared across sessions + Two independent sessions read and write files in the same persistent workspace. - - - - + + + + + + - - - - - - - - - - - - - - - - SESSION A - SESSION B - WORKSPACE - people.csv - script.py - output.json - + + + + + Session A + research conversation + + + + Session B + fresh conversation + + + + + + Workspace + + workspace_id + + + + people.csv + input + + + + script.py + reusable + + + + output.json + generated diff --git a/docs/cloud/images/v4-workspaces-light.excalidraw b/docs/cloud/images/v4-workspaces-light.excalidraw index 217dc485..a365f0a3 100644 --- a/docs/cloud/images/v4-workspaces-light.excalidraw +++ b/docs/cloud/images/v4-workspaces-light.excalidraw @@ -13,9 +13,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10302, @@ -37,18 +37,18 @@ "y": 170, "width": 202, "height": 34, - "text": "SESSION A", - "originalText": "SESSION A", + "text": "Session A", + "originalText": "Session A", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10303, @@ -72,9 +72,9 @@ "strokeColor": "#71717A", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10304, @@ -96,18 +96,18 @@ "y": 338, "width": 202, "height": 34, - "text": "SESSION B", - "originalText": "SESSION B", + "text": "Session B", + "originalText": "Session B", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10305, @@ -131,9 +131,9 @@ "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10306, @@ -169,9 +169,9 @@ "strokeColor": "#71717A", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10307, @@ -207,9 +207,9 @@ "strokeColor": "#FE750E", "backgroundColor": "#FFF8F4", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10308, @@ -231,18 +231,18 @@ "y": 145, "width": 496, "height": 30, - "text": "WORKSPACE", - "originalText": "WORKSPACE", + "text": "Workspace", + "originalText": "Workspace", "fontSize": 26, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10309, @@ -266,9 +266,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10310, @@ -293,15 +293,15 @@ "text": "people.csv", "originalText": "people.csv", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10311, @@ -325,9 +325,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10312, @@ -352,15 +352,15 @@ "text": "script.py", "originalText": "script.py", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10313, @@ -384,9 +384,9 @@ "strokeColor": "#52525B", "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 3, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10314, @@ -411,15 +411,15 @@ "text": "output.json", "originalText": "output.json", "fontSize": 21, - "fontFamily": 3, + "fontFamily": 2, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 1, + "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 2, + "roughness": 0, "opacity": 100, "angle": 0, "seed": 10315, diff --git a/docs/cloud/images/v4-workspaces-light.svg b/docs/cloud/images/v4-workspaces-light.svg index 277f5012..8ea00851 100644 --- a/docs/cloud/images/v4-workspaces-light.svg +++ b/docs/cloud/images/v4-workspaces-light.svg @@ -1,32 +1,46 @@ - - Sessions sharing a persistent workspace - Two independent sessions read and write files in one workspace. + + One workspace shared across sessions + Two independent sessions read and write files in the same persistent workspace. - - - - + + + + + + - - - - - - - - - - - - - - - - SESSION A - SESSION B - WORKSPACE - people.csv - script.py - output.json - + + + + + Session A + research conversation + + + + Session B + fresh conversation + + + + + + Workspace + + workspace_id + + + + people.csv + input + + + + script.py + reusable + + + + output.json + generated diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index e360d6c3..a1bd5954 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -646,41 +646,87 @@ default to recording off, and Zero Data Retention projects never record. 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. +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. 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). -## WebSocket URL +## Create, connect, and stop -Connect with a single URL. All configuration is passed as query parameters. +Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it +with the browser session ID. ### Playwright ```python Python +import os +import requests from playwright.sync_api import sync_playwright -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(WSS_URL) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) - browser.close() -# Browser is automatically stopped when the WebSocket disconnects +api_key = os.environ["BROWSER_USE_API_KEY"] +headers = {"X-Browser-Use-API-Key": api_key} +session = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +try: + with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(session["cdpUrl"]) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) +finally: + requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{session['id']}", + headers=headers, + json={"action": "stop"}, + ).raise_for_status() ``` ```typescript TypeScript import { chromium } from "playwright"; -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const session = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +try { + const browser = await chromium.connectOverCDP(session.cdpUrl); + const page = browser.contexts()[0].pages()[0]; + await page.goto("https://example.com"); + console.log(await page.title()); +} finally { + await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), + }); +} +``` +```bash curl +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"}') -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 +export BROWSER_SESSION_ID=$(echo "$session" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$session" | jq -r .cdpUrl) + +# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: +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"}' ``` ### Puppeteer @@ -688,13 +734,15 @@ 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 }); +// Create `session` with API V4 as shown above. +const browser = await puppeteer.connect({ + browserWSEndpoint: session.cdpUrl, +}); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); -await browser.close(); + +// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -702,19 +750,14 @@ await browser.close(); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. -## Query parameters + `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 + stop operation. Keep the returned browser session ID and call `PATCH + /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and + refunds unused browser time. -| 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. | - - Close the CDP connection when done. Browsers left running continue to incur - charges until their timeout expires. +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. # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 2812246c..18af4e9a 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -14,6 +14,12 @@ 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. + 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` - TypeScript: `npm install browser-use-sdk@latest` diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index de5fb845..b25a563f 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -253,6 +253,12 @@ cat > "$CLOUD_INDEX" << 'HEADER' 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. + 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` - TypeScript: `npm install browser-use-sdk@latest` diff --git a/docs/llms-full.txt b/docs/llms-full.txt index e360d6c3..a1bd5954 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -646,41 +646,87 @@ default to recording off, and Zero Data Retention projects never record. 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. +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. 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). -## WebSocket URL +## Create, connect, and stop -Connect with a single URL. All configuration is passed as query parameters. +Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it +with the browser session ID. ### Playwright ```python Python +import os +import requests from playwright.sync_api import sync_playwright -WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us" - -with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(WSS_URL) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) - browser.close() -# Browser is automatically stopped when the WebSocket disconnects +api_key = os.environ["BROWSER_USE_API_KEY"] +headers = {"X-Browser-Use-API-Key": api_key} +session = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +try: + with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(session["cdpUrl"]) + page = browser.contexts[0].pages[0] + page.goto("https://example.com") + print(page.title()) +finally: + requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{session['id']}", + headers=headers, + json={"action": "stop"}, + ).raise_for_status() ``` ```typescript TypeScript import { chromium } from "playwright"; -const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"; +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const session = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +try { + const browser = await chromium.connectOverCDP(session.cdpUrl); + const page = browser.contexts()[0].pages()[0]; + await page.goto("https://example.com"); + console.log(await page.title()); +} finally { + await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), + }); +} +``` +```bash curl +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"}') -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 +export BROWSER_SESSION_ID=$(echo "$session" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$session" | jq -r .cdpUrl) + +# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: +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"}' ``` ### Puppeteer @@ -688,13 +734,15 @@ 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 }); +// Create `session` with API V4 as shown above. +const browser = await puppeteer.connect({ + browserWSEndpoint: session.cdpUrl, +}); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); -await browser.close(); + +// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -702,19 +750,14 @@ await browser.close(); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. -## Query parameters + `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 + stop operation. Keep the returned browser session ID and call `PATCH + /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and + refunds unused browser time. -| 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. | - - Close the CDP connection when done. Browsers left running continue to incur - charges until their timeout expires. +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. # Profiles Source: https://docs.browser-use.com/cloud/guides/authentication diff --git a/docs/llms.txt b/docs/llms.txt index 2812246c..18af4e9a 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -14,6 +14,12 @@ 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. + 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` - TypeScript: `npm install browser-use-sdk@latest` From 6a019e6499d1a572e8491ea50ac26c4a65db3d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:11:07 -0700 Subject: [PATCH 05/15] revert: keep API v4 migration docs-only --- browser-use-node/src/generated/v4/types.ts | 7 ++- browser-use-node/src/v4.ts | 10 ++-- browser-use-node/src/v4/resources/runs.ts | 15 +----- browser-use-node/src/v4/resources/sessions.ts | 5 -- browser-use-node/tests/v4.test.ts | 51 ------------------- browser-use-node/tests/vibe.test.ts | 1 - .../browser_use_sdk/generated/v4/models.py | 10 ++-- .../src/browser_use_sdk/v4/resources/runs.py | 6 +-- .../browser_use_sdk/v4/resources/sessions.py | 8 --- browser-use-python/tests/test_v4.py | 36 ------------- browser-use-python/tests/test_vibe.py | 1 - snapshots/v4.json | 3 +- 12 files changed, 18 insertions(+), 135 deletions(-) diff --git a/browser-use-node/src/generated/v4/types.ts b/browser-use-node/src/generated/v4/types.ts index 5a896eb2..250eeea8 100644 --- a/browser-use-node/src/generated/v4/types.ts +++ b/browser-use-node/src/generated/v4/types.ts @@ -1110,7 +1110,12 @@ export interface components { * @default minimax-m3 * @enum {string} */ - model: "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" | "gemini-3.5-flash" | "gemini-3.1-pro" | "gemini-3-flash"; + // POST-GEN PATCH: "kimi-k3" and "claude-fable-5" omitted on purpose — + // live in the API but not public yet. openapi-typescript re-adds them + // from the spec on every regen, so re-apply this after `task gen:types` + // until the backend advertises a public model subset. (The API still + // accepts them; this only hides them from the SDK type.) + model: "glm-5.2" | "grok-4.5" | "minimax-m3" | "claude-opus-4.7" | "claude-opus-4.8" | "claude-sonnet-5" | "gpt-5.5" | "gpt-5.6" | "gemini-3.5-flash" | "gemini-3.1-pro" | "gemini-3-flash"; /** Sessionid */ sessionId?: string | null; /** Workspaceid */ diff --git a/browser-use-node/src/v4.ts b/browser-use-node/src/v4.ts index 8f136114..80f9d4e9 100644 --- a/browser-use-node/src/v4.ts +++ b/browser-use-node/src/v4.ts @@ -4,13 +4,7 @@ export type { BrowserUseOptions } from "./v4/client.js"; export { BrowserUseError } from "./core/errors.js"; export { Runs } from "./v4/resources/runs.js"; -export type { - RunBrowserSettings, - RunCreateRequest, - RunListParams, - RunEventsParams, - WaitOptions, -} from "./v4/resources/runs.js"; +export type { RunListParams, RunEventsParams, WaitOptions } from "./v4/resources/runs.js"; export { Sessions } from "./v4/resources/sessions.js"; export type { SessionListParams } from "./v4/resources/sessions.js"; @@ -25,6 +19,7 @@ import type { components } from "./generated/v4/types.js"; type S = components["schemas"]; // Run models +export type RunCreateRequest = S["RunCreateRequest"]; export type RunCreateResponse = S["RunCreateResponse"]; export type RunSummary = S["RunSummary"]; export type RunStatusResponse = S["RunStatusResponse"]; @@ -33,6 +28,7 @@ export type RunEvent = S["RunEvent"]; export type RunEventsResponse = S["RunEventsResponse"]; export type RunAttachment = S["RunAttachment"]; export type RunAttachmentsResponse = S["RunAttachmentsResponse"]; +export type RunBrowserSettings = S["RunBrowserSettings"]; export type RunJudgeSettings = S["RunJudgeSettings"]; // Session models diff --git a/browser-use-node/src/v4/resources/runs.ts b/browser-use-node/src/v4/resources/runs.ts index e24d4694..767ff404 100644 --- a/browser-use-node/src/v4/resources/runs.ts +++ b/browser-use-node/src/v4/resources/runs.ts @@ -1,20 +1,7 @@ import type { HttpClient } from "../../core/http.js"; import type { components } from "../../generated/v4/types.js"; -type GeneratedRunCreateRequest = components["schemas"]["RunCreateRequest"]; -type GeneratedRunBrowserSettings = components["schemas"]["RunBrowserSettings"]; -export type RunBrowserSettings = Omit & { - /** Defaults to US when omitted. Pass null to disable the managed proxy. */ - proxyCountryCode?: GeneratedRunBrowserSettings["proxyCountryCode"]; -}; -export type RunCreateRequest = Omit< - GeneratedRunCreateRequest, - "model" | "browserSettings" -> & { - /** Defaults to minimax-m3 when omitted. */ - model?: GeneratedRunCreateRequest["model"]; - browserSettings?: RunBrowserSettings | null; -}; +type RunCreateRequest = components["schemas"]["RunCreateRequest"]; type RunCreateResponse = components["schemas"]["RunCreateResponse"]; type RunSummary = components["schemas"]["RunSummary"]; type RunStatusResponse = components["schemas"]["RunStatusResponse"]; diff --git a/browser-use-node/src/v4/resources/sessions.ts b/browser-use-node/src/v4/resources/sessions.ts index 7185b026..487c7ef2 100644 --- a/browser-use-node/src/v4/resources/sessions.ts +++ b/browser-use-node/src/v4/resources/sessions.ts @@ -25,11 +25,6 @@ export class Sessions { return this.http.get(`/sessions/${sessionId}`); } - /** Immediately purge all data for a session. Available to ZDR projects only. */ - purge(sessionId: string): Promise { - return this.http.post(`/sessions/${sessionId}/purge`); - } - /** * Send a message to the session. Runs as the next turn when the session is * busy; set `interrupt: true` to cancel the active run so the message runs diff --git a/browser-use-node/tests/v4.test.ts b/browser-use-node/tests/v4.test.ts index 4dbcb269..a7a730e0 100644 --- a/browser-use-node/tests/v4.test.ts +++ b/browser-use-node/tests/v4.test.ts @@ -6,7 +6,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { Runs } from "../src/v4/resources/runs.js"; import { Sessions } from "../src/v4/resources/sessions.js"; import { Workspaces } from "../src/v4/resources/workspaces.js"; -import type { RunCreateRequest } from "../src/v4.js"; const RUN_ID = "00000000-0000-0000-0000-000000000001"; const SESSION_ID = "00000000-0000-0000-0000-000000000002"; @@ -33,45 +32,6 @@ function runSummary(status: string) { } describe("v4 runs.waitForCompletion", () => { - it("creates a run without requiring the API-defaulted model", async () => { - const http = { - post: vi.fn(async () => ({ - id: RUN_ID, - sessionId: SESSION_ID, - workspaceId: WORKSPACE_ID, - status: "queued", - })), - }; - const runs = new Runs(http as any); - const request: RunCreateRequest = { task: "Find the top HN post" }; - - await runs.create(request); - - expect(http.post).toHaveBeenCalledWith("/runs", { - task: "Find the top HN post", - }); - }); - - it("does not require proxyCountryCode for other browser settings", async () => { - const http = { - post: vi.fn(async () => ({ - id: RUN_ID, - sessionId: SESSION_ID, - workspaceId: WORKSPACE_ID, - status: "queued", - })), - }; - const runs = new Runs(http as any); - const request: RunCreateRequest = { - task: "Record this run", - browserSettings: { record: true }, - }; - - await runs.create(request); - - expect(http.post).toHaveBeenCalledWith("/runs", request); - }); - it("polls status until terminal, then fetches the full run once", async () => { const statuses = ["queued", "running", "completed"]; let statusCalls = 0; @@ -203,17 +163,6 @@ describe("v4 sessions queue", () => { expect(msg.status).toBe("pending"); }); - it("purges a session through the ZDR endpoint", async () => { - const http = { - post: vi.fn(async () => undefined), - }; - const sessions = new Sessions(http as any); - - await sessions.purge(SESSION_ID); - - expect(http.post).toHaveBeenCalledWith(`/sessions/${SESSION_ID}/purge`); - }); - it("lists pending queued messages", async () => { const http = { get: vi.fn(async () => ({ queue: [queuedMessage] })), diff --git a/browser-use-node/tests/vibe.test.ts b/browser-use-node/tests/vibe.test.ts index 883aa1bb..4891b06b 100644 --- a/browser-use-node/tests/vibe.test.ts +++ b/browser-use-node/tests/vibe.test.ts @@ -179,7 +179,6 @@ function v4EndpointToSdkMethod( // Sessions + queue if (method === "get" && path === "/sessions") return { resource: "sessions", method: "list" }; if (method === "get" && path === "/sessions/{session_id}") return { resource: "sessions", method: "get" }; - if (method === "post" && path === "/sessions/{session_id}/purge") return { resource: "sessions", method: "purge" }; if (method === "post" && path === "/sessions/{session_id}/queue") return { resource: "sessions", method: "sendMessage" }; if (method === "get" && path === "/sessions/{session_id}/queue") return { resource: "sessions", method: "queue" }; if (method === "delete" && path === "/sessions/{session_id}/queue/{message_id}") return { resource: "sessions", method: "removeMessage" }; diff --git a/browser-use-python/src/browser_use_sdk/generated/v4/models.py b/browser-use-python/src/browser_use_sdk/generated/v4/models.py index a00a2048..d6699071 100644 --- a/browser-use-python/src/browser_use_sdk/generated/v4/models.py +++ b/browser-use-python/src/browser_use_sdk/generated/v4/models.py @@ -657,15 +657,17 @@ class RunBrowserSettings(BaseModel): ) +# POST-GEN PATCH: kimi-k3 and claude-fable-5 are omitted on purpose — they are +# live in the API but not public yet. datamodel-codegen re-adds them from the +# spec on every regen, so this must be re-applied after `task gen:types` until +# the backend advertises a public model subset. (The API still accepts them; +# this only hides them from the SDK enum.) class Model(Enum): glm_5_2 = 'glm-5.2' grok_4_5 = 'grok-4.5' - kimi_k3 = 'kimi-k3' minimax_m3 = 'minimax-m3' claude_opus_4_7 = 'claude-opus-4.7' claude_opus_4_8 = 'claude-opus-4.8' - claude_opus_5 = 'claude-opus-5' - claude_fable_5 = 'claude-fable-5' claude_sonnet_5 = 'claude-sonnet-5' gpt_5_5 = 'gpt-5.5' gpt_5_6 = 'gpt-5.6' @@ -810,7 +812,7 @@ class ValidationError(BaseModel): class Name2(RootModel[str]): - root: str = Field(..., max_length=100, title='Name') + root: str = Field(..., max_length=255, title='Name') class WorkspaceCreateRequest(BaseModel): diff --git a/browser-use-python/src/browser_use_sdk/v4/resources/runs.py b/browser-use-python/src/browser_use_sdk/v4/resources/runs.py index 0fa6ac93..a7620010 100644 --- a/browser-use-python/src/browser_use_sdk/v4/resources/runs.py +++ b/browser-use-python/src/browser_use_sdk/v4/resources/runs.py @@ -42,11 +42,7 @@ def _build_create_body( body["workspaceId"] = str(workspace_id) if browser_settings is not None: if isinstance(browser_settings, RunBrowserSettings): - body["browserSettings"] = browser_settings.model_dump( - by_alias=True, - exclude_unset=True, - mode="json", - ) + body["browserSettings"] = browser_settings.model_dump(by_alias=True, exclude_none=True, mode="json") else: body["browserSettings"] = browser_settings if attached_file_ids is not None: diff --git a/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py b/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py index 392245a1..440a6976 100644 --- a/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py +++ b/browser-use-python/src/browser_use_sdk/v4/resources/sessions.py @@ -57,10 +57,6 @@ def get(self, session_id: str | UUID) -> SessionInfo: self._http.request("GET", f"/sessions/{session_id}") ) - def purge(self, session_id: str | UUID) -> None: - """Immediately purge all session data. Available to ZDR projects only.""" - self._http.request("POST", f"/sessions/{session_id}/purge") - def send_message( self, session_id: str | UUID, @@ -124,10 +120,6 @@ async def get(self, session_id: str | UUID) -> SessionInfo: await self._http.request("GET", f"/sessions/{session_id}") ) - async def purge(self, session_id: str | UUID) -> None: - """Immediately purge all session data. Available to ZDR projects only.""" - await self._http.request("POST", f"/sessions/{session_id}/purge") - async def send_message( self, session_id: str | UUID, diff --git a/browser-use-python/tests/test_v4.py b/browser-use-python/tests/test_v4.py index 87f15fc0..1f271768 100644 --- a/browser-use-python/tests/test_v4.py +++ b/browser-use-python/tests/test_v4.py @@ -9,7 +9,6 @@ import httpx import pytest -from browser_use_sdk.v4 import RunBrowserSettings from browser_use_sdk.v4.resources.runs import AsyncRuns, Runs from browser_use_sdk.v4.resources.sessions import Sessions from browser_use_sdk.v4.resources.workspaces import AsyncWorkspaces, Workspaces @@ -196,32 +195,6 @@ def test_runs_create_sends_camel_case_body() -> None: assert str(created.id) == RUN_ID -def test_runs_create_preserves_explicit_null_proxy() -> None: - http = FakeSyncHttp( - [ - { - "id": RUN_ID, - "status": "queued", - "model": "minimax-m3", - "sessionId": SESSION_ID, - "workspaceId": WORKSPACE_ID, - "eventsUrl": f"https://api.browser-use.com/api/v4/runs/{RUN_ID}/events", - } - ] - ) - runs = Runs(http) # type: ignore[arg-type] - - runs.create( - "Test staging", - browser_settings=RunBrowserSettings(proxyCountryCode=None), - ) - - assert http.calls[0][2] == { - "task": "Test staging", - "browserSettings": {"proxyCountryCode": None}, - } - - def test_runs_list_cursor_pagination() -> None: http = FakeSyncHttp( [ @@ -291,15 +264,6 @@ def test_sessions_send_message() -> None: assert msg.status.value == "pending" -def test_sessions_purge() -> None: - http = FakeSyncHttp([{}]) - sessions = Sessions(http) # type: ignore[arg-type] - - sessions.purge(SESSION_ID) - - assert http.calls[0][:2] == ("POST", f"/sessions/{SESSION_ID}/purge") - - def test_sessions_queue_list() -> None: http = FakeSyncHttp([{"queue": [_queued_message()]}]) sessions = Sessions(http) # type: ignore[arg-type] diff --git a/browser-use-python/tests/test_vibe.py b/browser-use-python/tests/test_vibe.py index f95b5750..8b8df3e6 100644 --- a/browser-use-python/tests/test_vibe.py +++ b/browser-use-python/tests/test_vibe.py @@ -144,7 +144,6 @@ def _load_spec(path: Path) -> Dict[str, Any]: # sessions + queue ("get", "/sessions"): ("sessions", "list"), ("get", "/sessions/{session_id}"): ("sessions", "get"), - ("post", "/sessions/{session_id}/purge"): ("sessions", "purge"), ("post", "/sessions/{session_id}/queue"): ("sessions", "send_message"), ("get", "/sessions/{session_id}/queue"): ("sessions", "queue"), ("delete", "/sessions/{session_id}/queue/{message_id}"): ("sessions", "remove_message"), diff --git a/snapshots/v4.json b/snapshots/v4.json index a6f769c3..25c4488d 100644 --- a/snapshots/v4.json +++ b/snapshots/v4.json @@ -3109,7 +3109,6 @@ "minimax-m3", "claude-opus-4.7", "claude-opus-4.8", - "claude-opus-5", "claude-fable-5", "claude-sonnet-5", "gpt-5.5", @@ -3754,7 +3753,7 @@ "anyOf": [ { "type": "string", - "maxLength": 100 + "maxLength": 255 }, { "type": "null" From 994cb9c5769a0c0d00379504e47e59c9690cefdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:17:06 -0700 Subject: [PATCH 06/15] docs: document current v4 sdk type workarounds --- docs/cloud/agent/human-in-the-loop.mdx | 1 + docs/cloud/agent/models.mdx | 12 +++-- docs/cloud/agent/quickstart.mdx | 8 +++- docs/cloud/agent/sessions.mdx | 2 + docs/cloud/agent/structured-output.mdx | 1 + docs/cloud/agent/workspaces.mdx | 1 + docs/cloud/browser/live-preview.mdx | 4 +- docs/cloud/browser/proxies.mdx | 12 +++++ docs/cloud/guides/2fa.mdx | 8 +++- docs/cloud/guides/authentication.mdx | 6 ++- docs/cloud/guides/profile-sync.mdx | 6 ++- docs/cloud/llms-full.txt | 65 +++++++++++++++++++++----- docs/cloud/llms.txt | 6 +++ docs/cloud/quickstart.mdx | 8 +++- docs/generate-llms-txt.sh | 6 +++ docs/llms-full.txt | 65 +++++++++++++++++++++----- docs/llms.txt | 6 +++ 17 files changed, 184 insertions(+), 33 deletions(-) diff --git a/docs/cloud/agent/human-in-the-loop.mdx b/docs/cloud/agent/human-in-the-loop.mdx index 66cc9638..3ff71354 100644 --- a/docs/cloud/agent/human-in-the-loop.mdx +++ b/docs/cloud/agent/human-in-the-loop.mdx @@ -34,6 +34,7 @@ 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, }); ``` diff --git a/docs/cloud/agent/models.mdx b/docs/cloud/agent/models.mdx index 92f8b92e..8971118a 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -24,24 +24,30 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c 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 run = client.runs.create( "Compare three project-management tools", - model="claude-opus-5", + model="grok-4.5", ) ``` ```typescript TypeScript const run = await client.runs.create({ task: "Compare three project-management tools", - model: "claude-opus-5", + model: "grok-4.5", }); ``` ```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":"Compare three PM tools","model":"claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` diff --git a/docs/cloud/agent/quickstart.mdx b/docs/cloud/agent/quickstart.mdx index 37f41304..3c621975 100644 --- a/docs/cloud/agent/quickstart.mdx +++ b/docs/cloud/agent/quickstart.mdx @@ -15,7 +15,10 @@ export BROWSER_USE_API_KEY=your_key from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -25,6 +28,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -33,7 +37,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` diff --git a/docs/cloud/agent/sessions.mdx b/docs/cloud/agent/sessions.mdx index 5c159bc6..ed43de9d 100644 --- a/docs/cloud/agent/sessions.mdx +++ b/docs/cloud/agent/sessions.mdx @@ -38,11 +38,13 @@ 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); diff --git a/docs/cloud/agent/structured-output.mdx b/docs/cloud/agent/structured-output.mdx index 2a17894f..2228353d 100644 --- a/docs/cloud/agent/structured-output.mdx +++ b/docs/cloud/agent/structured-output.mdx @@ -31,6 +31,7 @@ const Story = z.object({ const run = await client.runs.create({ task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); const result = await client.runs.waitForCompletion(run.id); const story = Story.parse(JSON.parse(result.result ?? "{}")); diff --git a/docs/cloud/agent/workspaces.mdx b/docs/cloud/agent/workspaces.mdx index 2d193d6e..b75a7711 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -44,6 +44,7 @@ const uploaded = await client.workspaces.upload( 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], }); diff --git a/docs/cloud/browser/live-preview.mdx b/docs/cloud/browser/live-preview.mdx index e3797117..f3d8ebce 100644 --- a/docs/cloud/browser/live-preview.mdx +++ b/docs/cloud/browser/live-preview.mdx @@ -27,6 +27,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -71,7 +72,8 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test the checkout flow", - browserSettings: { record: true }, + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, }); ``` ```bash curl diff --git a/docs/cloud/browser/proxies.mdx b/docs/cloud/browser/proxies.mdx index ee3d58c0..9327e4bd 100644 --- a/docs/cloud/browser/proxies.mdx +++ b/docs/cloud/browser/proxies.mdx @@ -7,6 +7,12 @@ icon: globe A US residential proxy is enabled by default. 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.v4 import BrowserUse @@ -23,6 +29,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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" }, }); ``` @@ -49,6 +56,7 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test my staging site", + model: "grok-4.5", browserSettings: { proxyCountryCode: null }, }); ``` @@ -68,6 +76,7 @@ run = client.runs.create( "port": 8080, "username": "user", "password": "pass", + "ignoreCertErrors": False, } }, ) @@ -75,12 +84,15 @@ run = client.runs.create( ```typescript TypeScript 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, }, }, }); diff --git a/docs/cloud/guides/2fa.mdx b/docs/cloud/guides/2fa.mdx index 3b1ab424..116268c3 100644 --- a/docs/cloud/guides/2fa.mdx +++ b/docs/cloud/guides/2fa.mdx @@ -21,7 +21,11 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Download my latest invoice", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` @@ -48,11 +52,13 @@ next_run = client.runs.create( ```typescript TypeScript 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, }); ``` diff --git a/docs/cloud/guides/authentication.mdx b/docs/cloud/guides/authentication.mdx index 74bb64c7..d7ed70a7 100644 --- a/docs/cloud/guides/authentication.mdx +++ b/docs/cloud/guides/authentication.mdx @@ -26,7 +26,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Open my account dashboard and summarize it", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); const result = await client.runs.waitForCompletion(run.id); console.log(result.result); diff --git a/docs/cloud/guides/profile-sync.mdx b/docs/cloud/guides/profile-sync.mdx index 1969b035..a23b88e8 100644 --- a/docs/cloud/guides/profile-sync.mdx +++ b/docs/cloud/guides/profile-sync.mdx @@ -29,7 +29,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Check my LinkedIn messages", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index a1bd5954..de760832 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -28,7 +28,10 @@ npm install browser-use-sdk from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -38,6 +41,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -46,7 +50,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` Sessions, workspaces, models, and observability. @@ -76,7 +80,10 @@ export BROWSER_USE_API_KEY=your_key from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -86,6 +93,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -94,7 +102,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` Install the SDK with `pip install browser-use-sdk` or @@ -141,23 +149,27 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c 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 run = client.runs.create( "Compare three project-management tools", - model="claude-opus-5", + model="grok-4.5", ) ``` ```typescript TypeScript const run = await client.runs.create({ task: "Compare three project-management tools", - model: "claude-opus-5", + model: "grok-4.5", }); ``` ```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":"Compare three PM tools","model":"claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` ## Bring your own key @@ -197,6 +209,7 @@ const Story = z.object({ const run = await client.runs.create({ task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); const result = await client.runs.waitForCompletion(run.id); const story = Story.parse(JSON.parse(result.result ?? "{}")); @@ -242,11 +255,13 @@ 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); @@ -300,6 +315,7 @@ const uploaded = await client.workspaces.upload( 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], }); @@ -411,6 +427,7 @@ 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, }); ``` @@ -483,6 +500,10 @@ Source: https://docs.browser-use.com/cloud/browser/proxies A US residential proxy is enabled by default. 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.v4 import BrowserUse @@ -498,6 +519,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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" }, }); ``` @@ -522,6 +544,7 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test my staging site", + model: "grok-4.5", browserSettings: { proxyCountryCode: null }, }); ``` @@ -539,6 +562,7 @@ run = client.runs.create( "port": 8080, "username": "user", "password": "pass", + "ignoreCertErrors": False, } }, ) @@ -546,12 +570,15 @@ run = client.runs.create( ```typescript TypeScript 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, }, }, }); @@ -587,6 +614,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -629,7 +657,8 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test the checkout flow", - browserSettings: { record: true }, + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, }); ``` ```bash curl @@ -784,7 +813,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Open my account dashboard and summarize it", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); const result = await client.runs.waitForCompletion(run.id); console.log(result.result); @@ -830,7 +863,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Check my LinkedIn messages", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` @@ -857,7 +894,11 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Download my latest invoice", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` @@ -882,11 +923,13 @@ next_run = client.runs.create( ```typescript TypeScript 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, }); ``` diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 18af4e9a..150f200d 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -20,6 +20,12 @@ 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` - TypeScript: `npm install browser-use-sdk@latest` diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index 12e3a4b4..410cf786 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -30,7 +30,10 @@ npm install browser-use-sdk from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -40,6 +43,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -48,7 +52,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index b25a563f..00580d24 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -259,6 +259,12 @@ 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` - TypeScript: `npm install browser-use-sdk@latest` diff --git a/docs/llms-full.txt b/docs/llms-full.txt index a1bd5954..de760832 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -28,7 +28,10 @@ npm install browser-use-sdk from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -38,6 +41,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -46,7 +50,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` Sessions, workspaces, models, and observability. @@ -76,7 +80,10 @@ export BROWSER_USE_API_KEY=your_key from browser_use_sdk.v4 import BrowserUse client = BrowserUse() -run = client.runs.create("Find the top Hacker News story") +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) ``` @@ -86,6 +93,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -94,7 +102,7 @@ console.log(result.result); 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"}' + -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` Install the SDK with `pip install browser-use-sdk` or @@ -141,23 +149,27 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c 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 run = client.runs.create( "Compare three project-management tools", - model="claude-opus-5", + model="grok-4.5", ) ``` ```typescript TypeScript const run = await client.runs.create({ task: "Compare three project-management tools", - model: "claude-opus-5", + model: "grok-4.5", }); ``` ```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":"Compare three PM tools","model":"claude-opus-5"}' + -d '{"task":"Compare three PM tools","model":"grok-4.5"}' ``` ## Bring your own key @@ -197,6 +209,7 @@ const Story = z.object({ const run = await client.runs.create({ task: 'Find the top HN story. Return only {"title":"...","points":0}.', + model: "grok-4.5", }); const result = await client.runs.waitForCompletion(run.id); const story = Story.parse(JSON.parse(result.result ?? "{}")); @@ -242,11 +255,13 @@ 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); @@ -300,6 +315,7 @@ const uploaded = await client.workspaces.upload( 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], }); @@ -411,6 +427,7 @@ 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, }); ``` @@ -483,6 +500,10 @@ Source: https://docs.browser-use.com/cloud/browser/proxies A US residential proxy is enabled by default. 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.v4 import BrowserUse @@ -498,6 +519,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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" }, }); ``` @@ -522,6 +544,7 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test my staging site", + model: "grok-4.5", browserSettings: { proxyCountryCode: null }, }); ``` @@ -539,6 +562,7 @@ run = client.runs.create( "port": 8080, "username": "user", "password": "pass", + "ignoreCertErrors": False, } }, ) @@ -546,12 +570,15 @@ run = client.runs.create( ```typescript TypeScript 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, }, }, }); @@ -587,6 +614,7 @@ import { BrowserUse } from "browser-use-sdk/v4"; 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); @@ -629,7 +657,8 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Test the checkout flow", - browserSettings: { record: true }, + model: "grok-4.5", + browserSettings: { proxyCountryCode: "us", record: true }, }); ``` ```bash curl @@ -784,7 +813,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Open my account dashboard and summarize it", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); const result = await client.runs.waitForCompletion(run.id); console.log(result.result); @@ -830,7 +863,11 @@ import { BrowserUse } from "browser-use-sdk/v4"; const client = new BrowserUse(); const run = await client.runs.create({ task: "Check my LinkedIn messages", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` @@ -857,7 +894,11 @@ run = client.runs.create( ```typescript TypeScript const run = await client.runs.create({ task: "Download my latest invoice", - browserSettings: { profileId: "YOUR_PROFILE_ID" }, + model: "grok-4.5", + browserSettings: { + profileId: "YOUR_PROFILE_ID", + proxyCountryCode: "us", + }, }); ``` @@ -882,11 +923,13 @@ next_run = client.runs.create( ```typescript TypeScript 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, }); ``` diff --git a/docs/llms.txt b/docs/llms.txt index 18af4e9a..150f200d 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -20,6 +20,12 @@ 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` - TypeScript: `npm install browser-use-sdk@latest` From eb09802403f7dd009af45278cb60be3c4df7866f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 20:09:14 -0700 Subject: [PATCH 07/15] docs: clarify cloud products and soften diagrams --- .../images/v4-agent-overview-dark.excalidraw | 20 ++-- docs/cloud/images/v4-agent-overview-dark.svg | 71 ++++++----- .../images/v4-agent-overview-light.excalidraw | 20 ++-- docs/cloud/images/v4-agent-overview-light.svg | 71 ++++++----- docs/cloud/images/v4-scripts-dark.excalidraw | 26 ++-- docs/cloud/images/v4-scripts-dark.svg | 90 ++++++++------ docs/cloud/images/v4-scripts-light.excalidraw | 26 ++-- docs/cloud/images/v4-scripts-light.svg | 90 ++++++++------ docs/cloud/images/v4-sessions-dark.excalidraw | 20 ++-- docs/cloud/images/v4-sessions-dark.svg | 59 ++++++---- .../cloud/images/v4-sessions-light.excalidraw | 20 ++-- docs/cloud/images/v4-sessions-light.svg | 59 ++++++---- .../images/v4-workspaces-dark.excalidraw | 28 ++--- docs/cloud/images/v4-workspaces-dark.svg | 78 +++++++----- .../images/v4-workspaces-light.excalidraw | 28 ++--- docs/cloud/images/v4-workspaces-light.svg | 78 +++++++----- docs/cloud/llms-full.txt | 89 +++++++++++++- docs/cloud/llms.txt | 9 +- docs/cloud/quickstart.mdx | 111 ++++++++++++++++-- docs/generate-llms-txt.sh | 7 +- docs/llms-full.txt | 89 +++++++++++++- docs/llms.txt | 9 +- 22 files changed, 742 insertions(+), 356 deletions(-) diff --git a/docs/cloud/images/v4-agent-overview-dark.excalidraw b/docs/cloud/images/v4-agent-overview-dark.excalidraw index 42b4d542..0a1216bb 100644 --- a/docs/cloud/images/v4-agent-overview-dark.excalidraw +++ b/docs/cloud/images/v4-agent-overview-dark.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10102, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10103, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10104, @@ -112,7 +112,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10105, @@ -145,7 +145,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10106, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10107, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10108, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10110, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10111, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10112, diff --git a/docs/cloud/images/v4-agent-overview-dark.svg b/docs/cloud/images/v4-agent-overview-dark.svg index 16828e12..08183bfc 100644 --- a/docs/cloud/images/v4-agent-overview-dark.svg +++ b/docs/cloud/images/v4-agent-overview-dark.svg @@ -1,38 +1,55 @@ - + Task, session, run, and workspace relationship A task starts a run inside a session. The run reads and writes files in a persistent workspace. + - - + + + + + + - + - - - Task + + + Task + - + + Session + + session_id - - Session - - session_id + + + + Run + conversation + live browser + events + result + + + + - - - - Run - conversation + live browser + - - - - Workspace - - - files - - - scripts + + Workspace + persistent across runs + + + files + + + scripts diff --git a/docs/cloud/images/v4-agent-overview-light.excalidraw b/docs/cloud/images/v4-agent-overview-light.excalidraw index 1a1268fe..10800081 100644 --- a/docs/cloud/images/v4-agent-overview-light.excalidraw +++ b/docs/cloud/images/v4-agent-overview-light.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10102, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10103, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10104, @@ -112,7 +112,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10105, @@ -145,7 +145,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10106, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10107, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10108, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10110, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10111, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10112, diff --git a/docs/cloud/images/v4-agent-overview-light.svg b/docs/cloud/images/v4-agent-overview-light.svg index 5e904f29..40ede55a 100644 --- a/docs/cloud/images/v4-agent-overview-light.svg +++ b/docs/cloud/images/v4-agent-overview-light.svg @@ -1,38 +1,55 @@ - + Task, session, run, and workspace relationship A task starts a run inside a session. The run reads and writes files in a persistent workspace. + - - + + + + + + - + - - - Task + + + Task + - + + Session + + session_id - - Session - - session_id + + + + Run + conversation + live browser + events + result + + + + - - - - Run - conversation + live browser + - - - - Workspace - - - files - - - scripts + + Workspace + persistent across runs + + + files + + + scripts diff --git a/docs/cloud/images/v4-scripts-dark.excalidraw b/docs/cloud/images/v4-scripts-dark.excalidraw index 45b448cd..feb5bd50 100644 --- a/docs/cloud/images/v4-scripts-dark.excalidraw +++ b/docs/cloud/images/v4-scripts-dark.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10401, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10402, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10403, @@ -112,7 +112,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10404, @@ -145,7 +145,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10405, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10406, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10407, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10408, @@ -263,7 +263,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10409, @@ -289,7 +289,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10410, @@ -327,7 +327,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10411, @@ -360,7 +360,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10412, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10413, diff --git a/docs/cloud/images/v4-scripts-dark.svg b/docs/cloud/images/v4-scripts-dark.svg index 246102b3..825d722e 100644 --- a/docs/cloud/images/v4-scripts-dark.svg +++ b/docs/cloud/images/v4-scripts-dark.svg @@ -1,42 +1,62 @@ - + Save and reuse tested scripts A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. + - - + + + + + + - - + + - - - - - - Run 01 - create · test · document - - - - - Workspace - - - script.py - - - README.md - - - - - - - Run 02+ - reuse first - - - - repair only if needed + + + + + + Run 01 + create the workflow + test it in the browser + save reuse instructions + + + + + Workspace + the reusable source of truth + + + + script.py + tested automation + + + + README.md + how to run it again + + + + + + + Run 02+ + reuse the saved script + skip repeated reasoning + finish faster for less + + + + repair only if the site changes diff --git a/docs/cloud/images/v4-scripts-light.excalidraw b/docs/cloud/images/v4-scripts-light.excalidraw index a287c25f..b91f8665 100644 --- a/docs/cloud/images/v4-scripts-light.excalidraw +++ b/docs/cloud/images/v4-scripts-light.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10401, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10402, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10403, @@ -112,7 +112,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10404, @@ -145,7 +145,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10405, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10406, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10407, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10408, @@ -263,7 +263,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10409, @@ -289,7 +289,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10410, @@ -327,7 +327,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10411, @@ -360,7 +360,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10412, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10413, diff --git a/docs/cloud/images/v4-scripts-light.svg b/docs/cloud/images/v4-scripts-light.svg index 0d1454f9..7e6bd2a9 100644 --- a/docs/cloud/images/v4-scripts-light.svg +++ b/docs/cloud/images/v4-scripts-light.svg @@ -1,42 +1,62 @@ - + Save and reuse tested scripts A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. + - - + + + + + + - - + + - - - - - - Run 01 - create · test · document - - - - - Workspace - - - script.py - - - README.md - - - - - - - Run 02+ - reuse first - - - - repair only if needed + + + + + + Run 01 + create the workflow + test it in the browser + save reuse instructions + + + + + Workspace + the reusable source of truth + + + + script.py + tested automation + + + + README.md + how to run it again + + + + + + + Run 02+ + reuse the saved script + skip repeated reasoning + finish faster for less + + + + repair only if the site changes diff --git a/docs/cloud/images/v4-sessions-dark.excalidraw b/docs/cloud/images/v4-sessions-dark.excalidraw index 13729dd3..bf128cc1 100644 --- a/docs/cloud/images/v4-sessions-dark.excalidraw +++ b/docs/cloud/images/v4-sessions-dark.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10202, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10203, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10204, @@ -107,7 +107,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10205, @@ -133,7 +133,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10206, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10207, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10208, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10209, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10210, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10211, diff --git a/docs/cloud/images/v4-sessions-dark.svg b/docs/cloud/images/v4-sessions-dark.svg index 48956d22..b1f75aa8 100644 --- a/docs/cloud/images/v4-sessions-dark.svg +++ b/docs/cloud/images/v4-sessions-dark.svg @@ -1,32 +1,47 @@ - + Multiple runs in one session Three sequential runs share one session ID, conversation, workspace, and live browser. + - - + + + + + + - - - Session - - ses_8f21… - + + + Session + + ses_8f21… - - - Run 01 - Open Hacker News + - - - Run 02 - Summarize the story + + + Run 01 + Open Hacker News + sessionId: ses_8f21… - - - Run 03 - Continue the task + + + Run 02 + Summarize the story + sessionId: ses_8f21… - same conversation · same workspace · same live browser + + + Run 03 + Continue the task + sessionId: ses_8f21… + + same conversation · same workspace · same live browser diff --git a/docs/cloud/images/v4-sessions-light.excalidraw b/docs/cloud/images/v4-sessions-light.excalidraw index 9b204a54..9b3a01ed 100644 --- a/docs/cloud/images/v4-sessions-light.excalidraw +++ b/docs/cloud/images/v4-sessions-light.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10202, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10203, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10204, @@ -107,7 +107,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10205, @@ -133,7 +133,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10206, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10207, @@ -204,7 +204,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10208, @@ -230,7 +230,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10209, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10210, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10211, diff --git a/docs/cloud/images/v4-sessions-light.svg b/docs/cloud/images/v4-sessions-light.svg index 48eeb532..7e3874cd 100644 --- a/docs/cloud/images/v4-sessions-light.svg +++ b/docs/cloud/images/v4-sessions-light.svg @@ -1,32 +1,47 @@ - + Multiple runs in one session Three sequential runs share one session ID, conversation, workspace, and live browser. + - - + + + + + + - - - Session - - ses_8f21… - + + + Session + + ses_8f21… - - - Run 01 - Open Hacker News + - - - Run 02 - Summarize the story + + + Run 01 + Open Hacker News + sessionId: ses_8f21… - - - Run 03 - Continue the task + + + Run 02 + Summarize the story + sessionId: ses_8f21… - same conversation · same workspace · same live browser + + + Run 03 + Continue the task + sessionId: ses_8f21… + + same conversation · same workspace · same live browser diff --git a/docs/cloud/images/v4-workspaces-dark.excalidraw b/docs/cloud/images/v4-workspaces-dark.excalidraw index 04d95d59..ab830f84 100644 --- a/docs/cloud/images/v4-workspaces-dark.excalidraw +++ b/docs/cloud/images/v4-workspaces-dark.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10302, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10303, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10304, @@ -107,7 +107,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10305, @@ -133,7 +133,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10306, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10307, @@ -209,7 +209,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10308, @@ -242,7 +242,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10309, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10310, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10311, @@ -327,7 +327,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10312, @@ -360,7 +360,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10313, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10314, @@ -419,7 +419,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10315, diff --git a/docs/cloud/images/v4-workspaces-dark.svg b/docs/cloud/images/v4-workspaces-dark.svg index 9f69525c..6effee75 100644 --- a/docs/cloud/images/v4-workspaces-dark.svg +++ b/docs/cloud/images/v4-workspaces-dark.svg @@ -1,46 +1,60 @@ - + One workspace shared across sessions Two independent sessions read and write files in the same persistent workspace. + - - + + + + + + - - + + - + - - - Session A - research conversation + + + Session A + research conversation + reads + writes files - - - Session B - fresh conversation + + + Session B + fresh conversation + reuses the same files - - + + - - Workspace - - workspace_id + + Workspace + persistent files shared across sessions + + workspace_id - - - people.csv - input + + + people.csv + input - - - script.py - reusable + + + script.py + reusable - - - output.json - generated + + + output.json + generated diff --git a/docs/cloud/images/v4-workspaces-light.excalidraw b/docs/cloud/images/v4-workspaces-light.excalidraw index a365f0a3..89a32640 100644 --- a/docs/cloud/images/v4-workspaces-light.excalidraw +++ b/docs/cloud/images/v4-workspaces-light.excalidraw @@ -15,7 +15,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10302, @@ -48,7 +48,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10303, @@ -74,7 +74,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10304, @@ -107,7 +107,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10305, @@ -133,7 +133,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10306, @@ -171,7 +171,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10307, @@ -209,7 +209,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10308, @@ -242,7 +242,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10309, @@ -268,7 +268,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10310, @@ -301,7 +301,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10311, @@ -327,7 +327,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10312, @@ -360,7 +360,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10313, @@ -386,7 +386,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10314, @@ -419,7 +419,7 @@ "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 0, + "roughness": 1, "opacity": 100, "angle": 0, "seed": 10315, diff --git a/docs/cloud/images/v4-workspaces-light.svg b/docs/cloud/images/v4-workspaces-light.svg index 8ea00851..11820869 100644 --- a/docs/cloud/images/v4-workspaces-light.svg +++ b/docs/cloud/images/v4-workspaces-light.svg @@ -1,46 +1,60 @@ - + One workspace shared across sessions Two independent sessions read and write files in the same persistent workspace. + - - + + + + + + - - + + - + - - - Session A - research conversation + + + Session A + research conversation + reads + writes files - - - Session B - fresh conversation + + + Session B + fresh conversation + reuses the same files - - + + - - Workspace - - workspace_id + + Workspace + persistent files shared across sessions + + workspace_id - - - people.csv - input + + + people.csv + input - - - script.py - reusable + + + script.py + reusable - - - output.json - generated + + + output.json + generated diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index de760832..111b31fd 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -5,15 +5,26 @@ Source: https://docs.browser-use.com/cloud/quickstart -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: +Browser Use Cloud gives you two ways to automate the web: + +Give it a goal and get the result. API V4 is built for hard, +high-accuracy work; API V2 trades accuracy for very low cost. +Control a managed browser directly with Playwright, Puppeteer, or another +remote CDP client. + + Both products run on the same browser infrastructure with stealth, + residential proxies, profiles, recordings, and live observability. + +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 ``` -## 1. Install +## Install the Agent SDK -Skip this step if you use curl. +Skip this step if you use curl or only need direct Browser control. ```bash Python pip install browser-use-sdk @@ -22,7 +33,9 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## 2. Run a task +## Run an agent + +Give API V4 a goal and get the result: ```python Python from browser_use_sdk.v4 import BrowserUse @@ -53,8 +66,72 @@ curl https://api.browser-use.com/api/v4/runs \ -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -Sessions, workspaces, models, and observability. -A compact, API V4-first context file for coding agents. +## Control a browser + +Create a standalone browser with API V4. Connect your CDP client to the +returned `cdpUrl`, then stop the browser with its `id`: + +```python Python +import os +import requests + +headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} +browser = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +print(browser["cdpUrl"]) + +# After your CDP client is finished: +requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", + headers=headers, + json={"action": "stop"}, +).raise_for_status() +``` +```typescript TypeScript +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +console.log(browser.cdpUrl); + +// After your CDP client is finished: +await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), +}); +``` +```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"}') + +echo "$browser" | jq -r .cdpUrl + +# After your CDP client is finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` + + Closing or disconnecting your CDP client does not stop the managed browser. + Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. + + Give your coding agent the compact API V4 context. # Prompt for Vibecoders Source: https://docs.browser-use.com/cloud/vibecoding diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 150f200d..6891c9b2 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -1,6 +1,11 @@ # Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `X-Browser-Use-API-Key` (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 @@ -37,7 +42,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a high-accuracy browser task with Python, TypeScript, or curl. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run an agent or control a cloud browser directly. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index 410cf786..18d92b0e 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -1,18 +1,37 @@ --- title: Quick start -description: "Run a high-accuracy browser task with Python, TypeScript, or curl." +description: "Run an agent or control a cloud browser directly." icon: rocket --- -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: +Browser Use Cloud gives you two ways to automate the web: + + + + Give it a goal and get the result. API V4 is built for hard, + high-accuracy work; API V2 trades accuracy for very low cost. + + + Control a managed browser directly with Playwright, Puppeteer, or another + remote CDP client. + + + + + Both products run on the same browser infrastructure with stealth, + residential proxies, profiles, recordings, and live observability. + + +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 ``` -## 1. Install +## Install the Agent SDK -Skip this step if you use curl. +Skip this step if you use curl or only need direct Browser control. ```bash Python @@ -23,7 +42,9 @@ npm install browser-use-sdk ``` -## 2. Run a task +## Run an agent + +Give API V4 a goal and get the result: ```python Python @@ -56,11 +77,75 @@ curl https://api.browser-use.com/api/v4/runs \ ``` - - - Sessions, workspaces, models, and observability. - - - A compact, API V4-first context file for coding agents. - - +## Control a browser + +Create a standalone browser with API V4. Connect your CDP client to the +returned `cdpUrl`, then stop the browser with its `id`: + + +```python Python +import os +import requests + +headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} +browser = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +print(browser["cdpUrl"]) + +# After your CDP client is finished: +requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", + headers=headers, + json={"action": "stop"}, +).raise_for_status() +``` +```typescript TypeScript +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +console.log(browser.cdpUrl); + +// After your CDP client is finished: +await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), +}); +``` +```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"}') + +echo "$browser" | jq -r .cdpUrl + +# After your CDP client is finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` + + + + Closing or disconnecting your CDP client does not stop the managed browser. + Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. + + + + Give your coding agent the compact API V4 context. + diff --git a/docs/generate-llms-txt.sh b/docs/generate-llms-txt.sh index 00580d24..18e05de7 100755 --- a/docs/generate-llms-txt.sh +++ b/docs/generate-llms-txt.sh @@ -239,7 +239,12 @@ CLOUD_FULL="$SCRIPT_DIR/llms-full.txt" cat > "$CLOUD_INDEX" << 'HEADER' # Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `X-Browser-Use-API-Key` (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 diff --git a/docs/llms-full.txt b/docs/llms-full.txt index de760832..111b31fd 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -5,15 +5,26 @@ Source: https://docs.browser-use.com/cloud/quickstart -Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys&new=1), then: +Browser Use Cloud gives you two ways to automate the web: + +Give it a goal and get the result. API V4 is built for hard, +high-accuracy work; API V2 trades accuracy for very low cost. +Control a managed browser directly with Playwright, Puppeteer, or another +remote CDP client. + + Both products run on the same browser infrastructure with stealth, + residential proxies, profiles, recordings, and live observability. + +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 ``` -## 1. Install +## Install the Agent SDK -Skip this step if you use curl. +Skip this step if you use curl or only need direct Browser control. ```bash Python pip install browser-use-sdk @@ -22,7 +33,9 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## 2. Run a task +## Run an agent + +Give API V4 a goal and get the result: ```python Python from browser_use_sdk.v4 import BrowserUse @@ -53,8 +66,72 @@ curl https://api.browser-use.com/api/v4/runs \ -d '{"task":"Find the top Hacker News story","model":"grok-4.5"}' ``` -Sessions, workspaces, models, and observability. -A compact, API V4-first context file for coding agents. +## Control a browser + +Create a standalone browser with API V4. Connect your CDP client to the +returned `cdpUrl`, then stop the browser with its `id`: + +```python Python +import os +import requests + +headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} +browser = requests.post( + "https://api.browser-use.com/api/v4/browsers", + headers=headers, + json={"proxyCountryCode": "us"}, +).json() + +print(browser["cdpUrl"]) + +# After your CDP client is finished: +requests.patch( + f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", + headers=headers, + json={"action": "stop"}, +).raise_for_status() +``` +```typescript TypeScript +const headers = { + "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, + "Content-Type": "application/json", +}; +const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { + method: "POST", + headers, + body: JSON.stringify({ proxyCountryCode: "us" }), +}).then((response) => response.json()) as { id: string; cdpUrl: string }; + +console.log(browser.cdpUrl); + +// After your CDP client is finished: +await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { + method: "PATCH", + headers, + body: JSON.stringify({ action: "stop" }), +}); +``` +```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"}') + +echo "$browser" | jq -r .cdpUrl + +# After your CDP client is finished: +curl -X PATCH \ + "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"action":"stop"}' +``` + + Closing or disconnecting your CDP client does not stop the managed browser. + Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + `{"action":"stop"}`. + + Give your coding agent the compact API V4 context. # Prompt for Vibecoders Source: https://docs.browser-use.com/cloud/vibecoding diff --git a/docs/llms.txt b/docs/llms.txt index 150f200d..6891c9b2 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,6 +1,11 @@ # Browser Use Cloud -> Browser Use Cloud is a managed API for AI browser automation. Send a natural-language task and get a completed run back. SDKs for Python and TypeScript. Auth via `X-Browser-Use-API-Key` (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 @@ -37,7 +42,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run a high-accuracy browser task with Python, TypeScript, or curl. +- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run an agent or control a cloud browser directly. - [Prompt for Vibecoders](https://docs.browser-use.com/cloud/vibecoding): Complete Cloud SDK reference for AI coding agents. ## Agent From 3cab96fddb53107042ebf9911681888210b8ae8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:38:44 -0700 Subject: [PATCH 08/15] docs: refine v4 cloud onboarding and diagrams --- .../browser/playwright-puppeteer-selenium.mdx | 106 +++---- .../images/v4-agent-overview-dark.excalidraw | 166 +++++----- docs/cloud/images/v4-agent-overview-dark.svg | 74 ++--- .../images/v4-agent-overview-light.excalidraw | 170 +++++----- docs/cloud/images/v4-agent-overview-light.svg | 74 ++--- docs/cloud/images/v4-scripts-dark.excalidraw | 239 ++++++-------- docs/cloud/images/v4-scripts-dark.svg | 85 ++--- docs/cloud/images/v4-scripts-light.excalidraw | 245 ++++++--------- docs/cloud/images/v4-scripts-light.svg | 85 ++--- docs/cloud/images/v4-sessions-dark.excalidraw | 164 +++++----- docs/cloud/images/v4-sessions-dark.svg | 67 ++-- .../cloud/images/v4-sessions-light.excalidraw | 174 +++++------ docs/cloud/images/v4-sessions-light.svg | 67 ++-- .../images/v4-workspaces-dark.excalidraw | 282 +++++------------ docs/cloud/images/v4-workspaces-dark.svg | 81 ++--- .../images/v4-workspaces-light.excalidraw | 294 ++++++------------ docs/cloud/images/v4-workspaces-light.svg | 81 ++--- docs/cloud/llms-full.txt | 181 ++++------- docs/cloud/llms.txt | 2 +- docs/cloud/quickstart.mdx | 87 ++---- docs/llms-full.txt | 181 ++++------- docs/llms.txt | 2 +- 22 files changed, 1074 insertions(+), 1833 deletions(-) diff --git a/docs/cloud/browser/playwright-puppeteer-selenium.mdx b/docs/cloud/browser/playwright-puppeteer-selenium.mdx index 28640013..dafbf96e 100644 --- a/docs/cloud/browser/playwright-puppeteer-selenium.mdx +++ b/docs/cloud/browser/playwright-puppeteer-selenium.mdx @@ -13,81 +13,42 @@ proxies](/cloud/browser/proxies) enabled by default. [create an API V4 run](/cloud/agent/quickstart). -## Create, connect, and stop +## 1. Create a browser -Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it -with the browser session ID. +```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 import os -import requests from playwright.sync_api import sync_playwright -api_key = os.environ["BROWSER_USE_API_KEY"] -headers = {"X-Browser-Use-API-Key": api_key} -session = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -try: - with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(session["cdpUrl"]) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) -finally: - requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{session['id']}", - headers=headers, - json={"action": "stop"}, - ).raise_for_status() +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"; -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const session = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; - -try { - const browser = await chromium.connectOverCDP(session.cdpUrl); - const page = browser.contexts()[0].pages()[0]; - await page.goto("https://example.com"); - console.log(await page.title()); -} finally { - await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), - }); -} -``` -```bash curl -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) - -# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: -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"}' +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()); ``` @@ -96,15 +57,12 @@ curl -X PATCH \ ```typescript import puppeteer from "puppeteer-core"; -// Create `session` with API V4 as shown above. const browser = await puppeteer.connect({ - browserWSEndpoint: session.cdpUrl, + browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!, }); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); - -// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -112,11 +70,19 @@ console.log(await page.title()); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. +## 3. Stop the browser + +```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"}' +``` + - `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 - stop operation. Keep the returned browser session ID and call `PATCH - /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and - refunds unused browser time. + `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 diff --git a/docs/cloud/images/v4-agent-overview-dark.excalidraw b/docs/cloud/images/v4-agent-overview-dark.excalidraw index 0a1216bb..bd6428e4 100644 --- a/docs/cloud/images/v4-agent-overview-dark.excalidraw +++ b/docs/cloud/images/v4-agent-overview-dark.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "task", - "x": 70, - "y": 165, + "x": 55, + "y": 160, "width": 225, - "height": 118, + "height": 120, "strokeColor": "#FE750E", "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10102, @@ -33,22 +33,22 @@ { "type": "text", "id": "taskText", - "x": 100, - "y": 206, - "width": 165, - "height": 33, - "text": "Task", - "originalText": "Task", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10103, @@ -65,16 +65,16 @@ { "type": "arrow", "id": "taskToRun", - "x": 300, - "y": 224, - "width": 91, + "x": 298, + "y": 220, + "width": 59, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10104, @@ -91,7 +91,7 @@ 0 ], [ - 91, + 59, 0 ] ], @@ -103,16 +103,16 @@ { "type": "rectangle", "id": "session", - "x": 398, - "y": 114, - "width": 326, - "height": 220, - "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "x": 370, + "y": 55, + "width": 420, + "height": 310, + "strokeColor": "#71717A", + "backgroundColor": "#111113", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10105, @@ -130,22 +130,22 @@ { "type": "text", "id": "sessionTitle", - "x": 426, - "y": 134, - "width": 270, - "height": 30, - "text": "Session", - "originalText": "Session", - "fontSize": 26, - "fontFamily": 2, - "textAlign": "center", + "x": 408, + "y": 79, + "width": 300, + "height": 50, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "left", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10106, @@ -162,16 +162,16 @@ { "type": "rectangle", "id": "run", - "x": 438, - "y": 183, - "width": 246, - "height": 78, - "strokeColor": "#A1A1AA", - "backgroundColor": "#18181B", + "x": 450, + "y": 165, + "width": 260, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10107, @@ -189,22 +189,22 @@ { "type": "text", "id": "runText", - "x": 468, - "y": 205, - "width": 186, - "height": 33, - "text": "Run", - "originalText": "Run", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10108, @@ -221,16 +221,16 @@ { "type": "arrow", "id": "runToWorkspace", - "x": 730, - "y": 224, + "x": 805, + "y": 220, "width": 89, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10110, @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "workspace", - "x": 826, - "y": 137, - "width": 292, - "height": 174, + "x": 910, + "y": 135, + "width": 235, + "height": 185, "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10111, @@ -286,22 +286,22 @@ { "type": "text", "id": "workspaceText", - "x": 858, - "y": 180, - "width": 228, - "height": 70, - "text": "WORKSPACE\nfiles", - "originalText": "WORKSPACE\nfiles", - "fontSize": 24, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10112, diff --git a/docs/cloud/images/v4-agent-overview-dark.svg b/docs/cloud/images/v4-agent-overview-dark.svg index 08183bfc..ab32a12e 100644 --- a/docs/cloud/images/v4-agent-overview-dark.svg +++ b/docs/cloud/images/v4-agent-overview-dark.svg @@ -1,55 +1,29 @@ - + Task, session, run, and workspace relationship - A task starts a run inside a session. The run reads and writes files in a persistent workspace. - + A task starts a run inside a session. The run reads and writes a persistent workspace. - - - + + + - - - - - - - - Task - - - - Session - - session_id - - - - - Run - conversation - live browser - events + result - - - - - - - - - Workspace - persistent across runs - - - files - - - scripts + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + diff --git a/docs/cloud/images/v4-agent-overview-light.excalidraw b/docs/cloud/images/v4-agent-overview-light.excalidraw index 10800081..65b2829e 100644 --- a/docs/cloud/images/v4-agent-overview-light.excalidraw +++ b/docs/cloud/images/v4-agent-overview-light.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "task", - "x": 70, - "y": 165, + "x": 55, + "y": 160, "width": 225, - "height": 118, + "height": 120, "strokeColor": "#FE750E", - "backgroundColor": "#FFF4EC", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10102, @@ -33,22 +33,22 @@ { "type": "text", "id": "taskText", - "x": 100, - "y": 206, - "width": 165, - "height": 33, - "text": "Task", - "originalText": "Task", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10103, @@ -65,16 +65,16 @@ { "type": "arrow", "id": "taskToRun", - "x": 300, - "y": 224, - "width": 91, + "x": 298, + "y": 220, + "width": 59, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10104, @@ -91,7 +91,7 @@ 0 ], [ - 91, + 59, 0 ] ], @@ -103,16 +103,16 @@ { "type": "rectangle", "id": "session", - "x": 398, - "y": 114, - "width": 326, - "height": 220, - "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "x": 370, + "y": 55, + "width": 420, + "height": 310, + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10105, @@ -130,22 +130,22 @@ { "type": "text", "id": "sessionTitle", - "x": 426, - "y": 134, - "width": 270, - "height": 30, - "text": "Session", - "originalText": "Session", - "fontSize": 26, - "fontFamily": 2, - "textAlign": "center", + "x": 408, + "y": 79, + "width": 300, + "height": 50, + "text": "SESSION", + "originalText": "SESSION", + "fontSize": 40, + "fontFamily": 3, + "textAlign": "left", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10106, @@ -162,16 +162,16 @@ { "type": "rectangle", "id": "run", - "x": 438, - "y": 183, - "width": 246, - "height": 78, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "x": 450, + "y": 165, + "width": 260, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10107, @@ -189,22 +189,22 @@ { "type": "text", "id": "runText", - "x": 468, - "y": 205, - "width": 186, - "height": 33, - "text": "Run", - "originalText": "Run", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10108, @@ -221,16 +221,16 @@ { "type": "arrow", "id": "runToWorkspace", - "x": 730, - "y": 224, + "x": 805, + "y": 220, "width": 89, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10110, @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "workspace", - "x": 826, - "y": 137, - "width": 292, - "height": 174, + "x": 910, + "y": 135, + "width": 235, + "height": 185, "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10111, @@ -286,22 +286,22 @@ { "type": "text", "id": "workspaceText", - "x": 858, - "y": 180, - "width": 228, - "height": 70, - "text": "WORKSPACE\nfiles", - "originalText": "WORKSPACE\nfiles", - "fontSize": 24, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10112, @@ -317,7 +317,7 @@ } ], "appState": { - "viewBackgroundColor": "#ffffff", + "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 index 40ede55a..4a5dc347 100644 --- a/docs/cloud/images/v4-agent-overview-light.svg +++ b/docs/cloud/images/v4-agent-overview-light.svg @@ -1,55 +1,29 @@ - + Task, session, run, and workspace relationship - A task starts a run inside a session. The run reads and writes files in a persistent workspace. - + A task starts a run inside a session. The run reads and writes a persistent workspace. - - - + + + - - - - - - - - Task - - - - Session - - session_id - - - - - Run - conversation - live browser - events + result - - - - - - - - - Workspace - persistent across runs - - - files - - - scripts + + + + + + + + + + + + + + + TASK + SESSION + RUN + WORKSPACE + diff --git a/docs/cloud/images/v4-scripts-dark.excalidraw b/docs/cloud/images/v4-scripts-dark.excalidraw index feb5bd50..e6cd7148 100644 --- a/docs/cloud/images/v4-scripts-dark.excalidraw +++ b/docs/cloud/images/v4-scripts-dark.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "firstRun", - "x": 65, - "y": 142, - "width": 220, - "height": 116, - "strokeColor": "#FE750E", - "backgroundColor": "#24140B", + "x": 55, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10401, @@ -33,22 +33,22 @@ { "type": "text", "id": "firstRunText", - "x": 104, - "y": 183, - "width": 142, - "height": 35, - "text": "Run 01", - "originalText": "Run 01", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10402, @@ -65,16 +65,16 @@ { "type": "arrow", "id": "saveArrow", - "x": 296, - "y": 200, - "width": 124, + "x": 298, + "y": 210, + "width": 99, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10403, @@ -91,7 +91,7 @@ 0 ], [ - 124, + 99, 0 ] ], @@ -103,16 +103,16 @@ { "type": "rectangle", "id": "workspace", - "x": 430, - "y": 65, - "width": 340, - "height": 270, + "x": 415, + "y": 86, + "width": 370, + "height": 236, "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10404, @@ -130,22 +130,22 @@ { "type": "text", "id": "workspaceText", - "x": 490, - "y": 91, - "width": 220, - "height": 35, - "text": "Workspace", - "originalText": "Workspace", - "fontSize": 26, - "fontFamily": 2, + "x": 415, + "y": 100, + "width": 370, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 38, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10405, @@ -163,15 +163,15 @@ "type": "rectangle", "id": "script", "x": 475, - "y": 157, - "width": 125, - "height": 112, + "y": 156, + "width": 250, + "height": 108, "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10406, @@ -189,22 +189,22 @@ { "type": "text", "id": "scriptText", - "x": 487, - "y": 198, - "width": 101, - "height": 31, - "text": "script.py", - "originalText": "script.py", - "fontSize": 22, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10407, @@ -218,78 +218,19 @@ "containerId": null, "lineHeight": 1.25 }, - { - "type": "rectangle", - "id": "readme", - "x": 615, - "y": 157, - "width": 110, - "height": 112, - "strokeColor": "#A1A1AA", - "backgroundColor": "#18181B", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10408, - "version": 1, - "versionNonce": 20408, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "readmeText", - "x": 623, - "y": 198, - "width": 94, - "height": 31, - "text": "README.md", - "originalText": "README.md", - "fontSize": 22, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#F4F4F5", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10409, - "version": 1, - "versionNonce": 20409, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "arrow", "id": "reuseArrow", - "x": 780, - "y": 200, - "width": 124, + "x": 803, + "y": 210, + "width": 99, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10410, @@ -306,7 +247,7 @@ 0 ], [ - 124, + 99, 0 ] ], @@ -318,16 +259,16 @@ { "type": "rectangle", "id": "laterRun", - "x": 915, - "y": 142, - "width": 220, - "height": 116, + "x": 920, + "y": 150, + "width": 225, + "height": 120, "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10411, @@ -345,22 +286,22 @@ { "type": "text", "id": "laterRunText", - "x": 954, - "y": 183, - "width": 142, - "height": 35, + "x": 920, + "y": 179, + "width": 225, + "height": 60, "text": "RUN 2+", "originalText": "RUN 2+", - "fontSize": 26, - "fontFamily": 2, + "fontSize": 44, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10412, @@ -377,16 +318,16 @@ { "type": "arrow", "id": "repairArrow", - "x": 1025, - "y": 270, - "width": 480, - "height": 74, + "x": 1035, + "y": 286, + "width": 472, + "height": 78, "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10413, @@ -403,16 +344,16 @@ 0 ], [ - -90, - 74 + -20, + 78 ], [ - -395, - 74 + -325, + 56 ], [ - -480, - 10 + -472, + -8 ] ], "startBinding": null, diff --git a/docs/cloud/images/v4-scripts-dark.svg b/docs/cloud/images/v4-scripts-dark.svg index 825d722e..2d3cb46c 100644 --- a/docs/cloud/images/v4-scripts-dark.svg +++ b/docs/cloud/images/v4-scripts-dark.svg @@ -1,62 +1,31 @@ - - Save and reuse tested scripts - A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. - + + Save, reuse, and repair a browser script + The first run saves a script in a workspace. Later runs reuse and repair it. - - - + + + - - - - - - - - - - - - Run 01 - create the workflow - test it in the browser - save reuse instructions - - - - - Workspace - the reusable source of truth - - - - script.py - tested automation - - - - README.md - how to run it again - - - - - - - Run 02+ - reuse the saved script - skip repeated reasoning - finish faster for less - - - - repair only if the site changes + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + SCRIPT + RUN 2+ + diff --git a/docs/cloud/images/v4-scripts-light.excalidraw b/docs/cloud/images/v4-scripts-light.excalidraw index b91f8665..2702bbdc 100644 --- a/docs/cloud/images/v4-scripts-light.excalidraw +++ b/docs/cloud/images/v4-scripts-light.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "firstRun", - "x": 65, - "y": 142, - "width": 220, - "height": 116, - "strokeColor": "#FE750E", - "backgroundColor": "#FFF4EC", + "x": 55, + "y": 150, + "width": 225, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10401, @@ -33,22 +33,22 @@ { "type": "text", "id": "firstRunText", - "x": 104, - "y": 183, - "width": 142, - "height": 35, - "text": "Run 01", - "originalText": "Run 01", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10402, @@ -65,16 +65,16 @@ { "type": "arrow", "id": "saveArrow", - "x": 296, - "y": 200, - "width": 124, + "x": 298, + "y": 210, + "width": 99, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10403, @@ -91,7 +91,7 @@ 0 ], [ - 124, + 99, 0 ] ], @@ -103,16 +103,16 @@ { "type": "rectangle", "id": "workspace", - "x": 430, - "y": 65, - "width": 340, - "height": 270, + "x": 415, + "y": 86, + "width": 370, + "height": 236, "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10404, @@ -130,22 +130,22 @@ { "type": "text", "id": "workspaceText", - "x": 490, - "y": 91, - "width": 220, - "height": 35, - "text": "Workspace", - "originalText": "Workspace", - "fontSize": 26, - "fontFamily": 2, + "x": 415, + "y": 100, + "width": 370, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 38, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10405, @@ -163,15 +163,15 @@ "type": "rectangle", "id": "script", "x": 475, - "y": 157, - "width": 125, - "height": 112, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "y": 156, + "width": 250, + "height": 108, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10406, @@ -189,22 +189,22 @@ { "type": "text", "id": "scriptText", - "x": 487, - "y": 198, - "width": 101, - "height": 31, - "text": "script.py", - "originalText": "script.py", - "fontSize": 22, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10407, @@ -218,78 +218,19 @@ "containerId": null, "lineHeight": 1.25 }, - { - "type": "rectangle", - "id": "readme", - "x": 615, - "y": 157, - "width": 110, - "height": 112, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10408, - "version": 1, - "versionNonce": 20408, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "readmeText", - "x": 623, - "y": 198, - "width": 94, - "height": 31, - "text": "README.md", - "originalText": "README.md", - "fontSize": 22, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#18181B", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10409, - "version": 1, - "versionNonce": 20409, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, { "type": "arrow", "id": "reuseArrow", - "x": 780, - "y": 200, - "width": 124, + "x": 803, + "y": 210, + "width": 99, "height": 0, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10410, @@ -306,7 +247,7 @@ 0 ], [ - 124, + 99, 0 ] ], @@ -318,16 +259,16 @@ { "type": "rectangle", "id": "laterRun", - "x": 915, - "y": 142, - "width": 220, - "height": 116, + "x": 920, + "y": 150, + "width": 225, + "height": 120, "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10411, @@ -345,22 +286,22 @@ { "type": "text", "id": "laterRunText", - "x": 954, - "y": 183, - "width": 142, - "height": 35, + "x": 920, + "y": 179, + "width": 225, + "height": 60, "text": "RUN 2+", "originalText": "RUN 2+", - "fontSize": 26, - "fontFamily": 2, + "fontSize": 44, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10412, @@ -377,16 +318,16 @@ { "type": "arrow", "id": "repairArrow", - "x": 1025, - "y": 270, - "width": 480, - "height": 74, + "x": 1035, + "y": 286, + "width": 472, + "height": 78, "strokeColor": "#FE750E", "backgroundColor": "transparent", "fillStyle": "solid", "strokeWidth": 2, "strokeStyle": "dashed", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10413, @@ -403,16 +344,16 @@ 0 ], [ - -90, - 74 + -20, + 78 ], [ - -395, - 74 + -325, + 56 ], [ - -480, - 10 + -472, + -8 ] ], "startBinding": null, @@ -422,7 +363,7 @@ } ], "appState": { - "viewBackgroundColor": "#ffffff", + "viewBackgroundColor": "#FFFFFF", "gridSize": null }, "files": {} diff --git a/docs/cloud/images/v4-scripts-light.svg b/docs/cloud/images/v4-scripts-light.svg index 7e6bd2a9..d34a5600 100644 --- a/docs/cloud/images/v4-scripts-light.svg +++ b/docs/cloud/images/v4-scripts-light.svg @@ -1,62 +1,31 @@ - - Save and reuse tested scripts - A first run creates and tests a script in a workspace. Later runs reuse it and repair it only if the site changes. - + + Save, reuse, and repair a browser script + The first run saves a script in a workspace. Later runs reuse and repair it. - - - + + + - - - - - - - - - - - - Run 01 - create the workflow - test it in the browser - save reuse instructions - - - - - Workspace - the reusable source of truth - - - - script.py - tested automation - - - - README.md - how to run it again - - - - - - - Run 02+ - reuse the saved script - skip repeated reasoning - finish faster for less - - - - repair only if the site changes + + + + + + + + + + + + + + + + + RUN 1 + WORKSPACE + SCRIPT + RUN 2+ + diff --git a/docs/cloud/images/v4-sessions-dark.excalidraw b/docs/cloud/images/v4-sessions-dark.excalidraw index bf128cc1..8681d95b 100644 --- a/docs/cloud/images/v4-sessions-dark.excalidraw +++ b/docs/cloud/images/v4-sessions-dark.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "session", - "x": 66, - "y": 112, - "width": 1048, - "height": 250, - "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "x": 55, + "y": 55, + "width": 1090, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#111113", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10202, @@ -33,22 +33,22 @@ { "type": "text", "id": "sessionLabel", - "x": 98, - "y": 132, - "width": 220, - "height": 29, - "text": "Session ID", - "originalText": "Session ID", - "fontSize": 26, - "fontFamily": 2, + "x": 92, + "y": 76, + "width": 300, + "height": 53, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 42, + "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10203, @@ -65,16 +65,16 @@ { "type": "rectangle", "id": "run1", - "x": 106, - "y": 194, - "width": 245, - "height": 90, - "strokeColor": "#A1A1AA", - "backgroundColor": "#18181B", + "x": 105, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10204, @@ -92,22 +92,22 @@ { "type": "text", "id": "run1Text", - "x": 128, - "y": 221, - "width": 201, - "height": 34, - "text": "Run 01", - "originalText": "Run 01", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10205, @@ -124,16 +124,16 @@ { "type": "arrow", "id": "arrow1", - "x": 357, - "y": 239, - "width": 87, + "x": 375, + "y": 215, + "width": 100, "height": 0, "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10206, @@ -150,7 +150,7 @@ 0 ], [ - 87, + 100, 0 ] ], @@ -162,16 +162,16 @@ { "type": "rectangle", "id": "run2", - "x": 450, - "y": 194, - "width": 280, - "height": 90, + "x": 485, + "y": 155, + "width": 230, + "height": 120, "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10207, @@ -189,22 +189,22 @@ { "type": "text", "id": "run2Text", - "x": 472, - "y": 221, - "width": 236, - "height": 34, - "text": "Run 02", - "originalText": "Run 02", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10208, @@ -221,16 +221,16 @@ { "type": "arrow", "id": "arrow2", - "x": 736, - "y": 239, - "width": 87, + "x": 735, + "y": 215, + "width": 100, "height": 0, "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10209, @@ -247,7 +247,7 @@ 0 ], [ - 87, + 100, 0 ] ], @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "run3", - "x": 829, - "y": 194, - "width": 245, - "height": 90, + "x": 845, + "y": 155, + "width": 250, + "height": 120, "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10210, @@ -286,22 +286,22 @@ { "type": "text", "id": "run3Text", - "x": 851, - "y": 221, - "width": 201, - "height": 34, - "text": "Run 03", - "originalText": "Run 03", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10211, diff --git a/docs/cloud/images/v4-sessions-dark.svg b/docs/cloud/images/v4-sessions-dark.svg index b1f75aa8..1f5e9aff 100644 --- a/docs/cloud/images/v4-sessions-dark.svg +++ b/docs/cloud/images/v4-sessions-dark.svg @@ -1,47 +1,28 @@ - - Multiple runs in one session - Three sequential runs share one session ID, conversation, workspace, and live browser. - + + One session with multiple runs + One session ID contains three sequential runs. - - - + + + - - - - - - Session - - ses_8f21… - - - - - - Run 01 - Open Hacker News - sessionId: ses_8f21… - - - - Run 02 - Summarize the story - sessionId: ses_8f21… - - - - Run 03 - Continue the task - sessionId: ses_8f21… - - same conversation · same workspace · same live browser + + + + + + + + + + + + + + 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 index 9b3a01ed..b36615f2 100644 --- a/docs/cloud/images/v4-sessions-light.excalidraw +++ b/docs/cloud/images/v4-sessions-light.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "session", - "x": 66, - "y": 112, - "width": 1048, - "height": 250, - "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "x": 55, + "y": 55, + "width": 1090, + "height": 290, + "strokeColor": "#71717A", + "backgroundColor": "#FAFAFA", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "dashed", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10202, @@ -33,22 +33,22 @@ { "type": "text", "id": "sessionLabel", - "x": 98, - "y": 132, - "width": 220, - "height": 29, - "text": "Session ID", - "originalText": "Session ID", - "fontSize": 26, - "fontFamily": 2, + "x": 92, + "y": 76, + "width": 300, + "height": 53, + "text": "SESSION ID", + "originalText": "SESSION ID", + "fontSize": 42, + "fontFamily": 3, "textAlign": "left", "verticalAlign": "top", - "strokeColor": "#FE750E", + "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10203, @@ -65,16 +65,16 @@ { "type": "rectangle", "id": "run1", - "x": 106, - "y": 194, - "width": 245, - "height": 90, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "x": 105, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#FE750E", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10204, @@ -92,22 +92,22 @@ { "type": "text", "id": "run1Text", - "x": 128, - "y": 221, - "width": 201, - "height": 34, - "text": "Run 01", - "originalText": "Run 01", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10205, @@ -124,16 +124,16 @@ { "type": "arrow", "id": "arrow1", - "x": 357, - "y": 239, - "width": 87, + "x": 375, + "y": 215, + "width": 100, "height": 0, "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10206, @@ -150,7 +150,7 @@ 0 ], [ - 87, + 100, 0 ] ], @@ -162,16 +162,16 @@ { "type": "rectangle", "id": "run2", - "x": 450, - "y": 194, - "width": 280, - "height": 90, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "x": 485, + "y": 155, + "width": 230, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10207, @@ -189,22 +189,22 @@ { "type": "text", "id": "run2Text", - "x": 472, - "y": 221, - "width": 236, - "height": 34, - "text": "Run 02", - "originalText": "Run 02", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10208, @@ -221,16 +221,16 @@ { "type": "arrow", "id": "arrow2", - "x": 736, - "y": 239, - "width": 87, + "x": 735, + "y": 215, + "width": 100, "height": 0, "strokeColor": "#52525B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10209, @@ -247,7 +247,7 @@ 0 ], [ - 87, + 100, 0 ] ], @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "run3", - "x": 829, - "y": 194, - "width": 245, - "height": 90, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "x": 845, + "y": 155, + "width": 250, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10210, @@ -286,22 +286,22 @@ { "type": "text", "id": "run3Text", - "x": 851, - "y": 221, - "width": 201, - "height": 34, - "text": "Run 03", - "originalText": "Run 03", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10211, @@ -317,7 +317,7 @@ } ], "appState": { - "viewBackgroundColor": "#ffffff", + "viewBackgroundColor": "#FFFFFF", "gridSize": null }, "files": {} diff --git a/docs/cloud/images/v4-sessions-light.svg b/docs/cloud/images/v4-sessions-light.svg index 7e3874cd..158c2839 100644 --- a/docs/cloud/images/v4-sessions-light.svg +++ b/docs/cloud/images/v4-sessions-light.svg @@ -1,47 +1,28 @@ - - Multiple runs in one session - Three sequential runs share one session ID, conversation, workspace, and live browser. - + + One session with multiple runs + One session ID contains three sequential runs. - - - + + + - - - - - - Session - - ses_8f21… - - - - - - Run 01 - Open Hacker News - sessionId: ses_8f21… - - - - Run 02 - Summarize the story - sessionId: ses_8f21… - - - - Run 03 - Continue the task - sessionId: ses_8f21… - - same conversation · same workspace · same live browser + + + + + + + + + + + + + + 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 index ab830f84..d80f52ad 100644 --- a/docs/cloud/images/v4-workspaces-dark.excalidraw +++ b/docs/cloud/images/v4-workspaces-dark.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "session1", - "x": 74, - "y": 132, - "width": 270, - "height": 112, + "x": 60, + "y": 82, + "width": 280, + "height": 120, "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10302, @@ -33,22 +33,22 @@ { "type": "text", "id": "session1Text", - "x": 108, - "y": 170, - "width": 202, - "height": 34, - "text": "Session A", - "originalText": "Session A", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10303, @@ -65,16 +65,16 @@ { "type": "rectangle", "id": "session2", - "x": 74, - "y": 300, - "width": 270, - "height": 112, + "x": 60, + "y": 292, + "width": 280, + "height": 120, "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "dashed", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10304, @@ -92,22 +92,22 @@ { "type": "text", "id": "session2Text", - "x": 108, - "y": 338, - "width": 202, - "height": 34, - "text": "Session B", - "originalText": "Session B", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10305, @@ -124,16 +124,16 @@ { "type": "arrow", "id": "arrow1", - "x": 350, - "y": 188, - "width": 165, + "x": 355, + "y": 142, + "width": 112, "height": 78, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10306, @@ -150,7 +150,7 @@ 0 ], [ - 165, + 112, 78 ] ], @@ -162,16 +162,16 @@ { "type": "arrow", "id": "arrow2", - "x": 350, - "y": 356, - "width": 165, + "x": 355, + "y": 352, + "width": 112, "height": -78, "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "dashed", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10307, @@ -188,7 +188,7 @@ 0 ], [ - 165, + 112, -78 ] ], @@ -200,16 +200,16 @@ { "type": "rectangle", "id": "workspace", - "x": 522, - "y": 122, - "width": 584, - "height": 300, + "x": 480, + "y": 88, + "width": 655, + "height": 332, "strokeColor": "#FE750E", - "backgroundColor": "#1D1714", + "backgroundColor": "#24140B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10308, @@ -227,22 +227,22 @@ { "type": "text", "id": "workspaceTitle", - "x": 566, - "y": 145, - "width": 496, - "height": 30, - "text": "Workspace", - "originalText": "Workspace", - "fontSize": 26, - "fontFamily": 2, + "x": 480, + "y": 106, + "width": 655, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 40, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#F4F4F5", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10309, @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "file1", - "x": 577, - "y": 211, - "width": 142, - "height": 95, + "x": 610, + "y": 188, + "width": 395, + "height": 145, "strokeColor": "#A1A1AA", "backgroundColor": "#18181B", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10310, @@ -286,22 +286,22 @@ { "type": "text", "id": "file1Text", - "x": 596, - "y": 240, - "width": 104, - "height": 39, - "text": "people.csv", - "originalText": "people.csv", - "fontSize": 21, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10311, @@ -314,124 +314,6 @@ "locked": false, "containerId": null, "lineHeight": 1.25 - }, - { - "type": "rectangle", - "id": "file2", - "x": 743, - "y": 211, - "width": 142, - "height": 95, - "strokeColor": "#A1A1AA", - "backgroundColor": "#18181B", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10312, - "version": 1, - "versionNonce": 20312, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "file2Text", - "x": 762, - "y": 240, - "width": 104, - "height": 39, - "text": "script.py", - "originalText": "script.py", - "fontSize": 21, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#F4F4F5", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10313, - "version": 1, - "versionNonce": 20313, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, - { - "type": "rectangle", - "id": "file3", - "x": 909, - "y": 211, - "width": 142, - "height": 95, - "strokeColor": "#A1A1AA", - "backgroundColor": "#18181B", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10314, - "version": 1, - "versionNonce": 20314, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "file3Text", - "x": 928, - "y": 240, - "width": 104, - "height": 39, - "text": "output.json", - "originalText": "output.json", - "fontSize": 21, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#F4F4F5", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10315, - "version": 1, - "versionNonce": 20315, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 } ], "appState": { diff --git a/docs/cloud/images/v4-workspaces-dark.svg b/docs/cloud/images/v4-workspaces-dark.svg index 6effee75..071f27af 100644 --- a/docs/cloud/images/v4-workspaces-dark.svg +++ b/docs/cloud/images/v4-workspaces-dark.svg @@ -1,60 +1,29 @@ - - One workspace shared across sessions - Two independent sessions read and write files in the same persistent workspace. - + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. - - - + + + - - - - - - - - - - - Session A - research conversation - reads + writes files - - - - Session B - fresh conversation - reuses the same files - - - - - - Workspace - persistent files shared across sessions - - workspace_id - - - - people.csv - input - - - - script.py - reusable - - - - output.json - generated + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + FILES + diff --git a/docs/cloud/images/v4-workspaces-light.excalidraw b/docs/cloud/images/v4-workspaces-light.excalidraw index 89a32640..7f8810a9 100644 --- a/docs/cloud/images/v4-workspaces-light.excalidraw +++ b/docs/cloud/images/v4-workspaces-light.excalidraw @@ -6,16 +6,16 @@ { "type": "rectangle", "id": "session1", - "x": 74, - "y": 132, - "width": 270, - "height": 112, + "x": 60, + "y": 82, + "width": 280, + "height": 120, "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10302, @@ -33,22 +33,22 @@ { "type": "text", "id": "session1Text", - "x": 108, - "y": 170, - "width": 202, - "height": 34, - "text": "Session A", - "originalText": "Session A", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10303, @@ -65,16 +65,16 @@ { "type": "rectangle", "id": "session2", - "x": 74, - "y": 300, - "width": 270, - "height": 112, - "strokeColor": "#71717A", - "backgroundColor": "#FAFAFA", + "x": 60, + "y": 292, + "width": 280, + "height": 120, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "dashed", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10304, @@ -92,22 +92,22 @@ { "type": "text", "id": "session2Text", - "x": 108, - "y": 338, - "width": 202, - "height": 34, - "text": "Session B", - "originalText": "Session B", - "fontSize": 26, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10305, @@ -124,16 +124,16 @@ { "type": "arrow", "id": "arrow1", - "x": 350, - "y": 188, - "width": 165, + "x": 355, + "y": 142, + "width": 112, "height": 78, - "strokeColor": "#FE750E", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10306, @@ -150,7 +150,7 @@ 0 ], [ - 165, + 112, 78 ] ], @@ -162,16 +162,16 @@ { "type": "arrow", "id": "arrow2", - "x": 350, - "y": 356, - "width": 165, + "x": 355, + "y": 352, + "width": 112, "height": -78, - "strokeColor": "#71717A", + "strokeColor": "#A1A1AA", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "dashed", - "roughness": 1, + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10307, @@ -188,7 +188,7 @@ 0 ], [ - 165, + 112, -78 ] ], @@ -200,16 +200,16 @@ { "type": "rectangle", "id": "workspace", - "x": 522, - "y": 122, - "width": 584, - "height": 300, + "x": 480, + "y": 88, + "width": 655, + "height": 332, "strokeColor": "#FE750E", - "backgroundColor": "#FFF8F4", + "backgroundColor": "#FFF3E8", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10308, @@ -227,22 +227,22 @@ { "type": "text", "id": "workspaceTitle", - "x": 566, - "y": 145, - "width": 496, - "height": 30, - "text": "Workspace", - "originalText": "Workspace", - "fontSize": 26, - "fontFamily": 2, + "x": 480, + "y": 106, + "width": 655, + "height": 50, + "text": "WORKSPACE", + "originalText": "WORKSPACE", + "fontSize": 40, + "fontFamily": 3, "textAlign": "center", "verticalAlign": "middle", - "strokeColor": "#FE750E", + "strokeColor": "#18181B", "backgroundColor": "transparent", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10309, @@ -259,16 +259,16 @@ { "type": "rectangle", "id": "file1", - "x": 577, - "y": 211, - "width": 142, - "height": 95, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", + "x": 610, + "y": 188, + "width": 395, + "height": 145, + "strokeColor": "#A1A1AA", + "backgroundColor": "#FFFFFF", "fillStyle": "solid", - "strokeWidth": 2, + "strokeWidth": 3, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10310, @@ -286,22 +286,22 @@ { "type": "text", "id": "file1Text", - "x": 596, - "y": 240, - "width": 104, - "height": 39, - "text": "people.csv", - "originalText": "people.csv", - "fontSize": 21, - "fontFamily": 2, + "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": 2, + "strokeWidth": 1, "strokeStyle": "solid", - "roughness": 1, + "roughness": 2, "opacity": 100, "angle": 0, "seed": 10311, @@ -314,128 +314,10 @@ "locked": false, "containerId": null, "lineHeight": 1.25 - }, - { - "type": "rectangle", - "id": "file2", - "x": 743, - "y": 211, - "width": 142, - "height": 95, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10312, - "version": 1, - "versionNonce": 20312, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "file2Text", - "x": 762, - "y": 240, - "width": 104, - "height": 39, - "text": "script.py", - "originalText": "script.py", - "fontSize": 21, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#18181B", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10313, - "version": 1, - "versionNonce": 20313, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 - }, - { - "type": "rectangle", - "id": "file3", - "x": 909, - "y": 211, - "width": 142, - "height": 95, - "strokeColor": "#52525B", - "backgroundColor": "#FAFAFA", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10314, - "version": 1, - "versionNonce": 20314, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "roundness": { - "type": 3 - } - }, - { - "type": "text", - "id": "file3Text", - "x": 928, - "y": 240, - "width": 104, - "height": 39, - "text": "output.json", - "originalText": "output.json", - "fontSize": 21, - "fontFamily": 2, - "textAlign": "center", - "verticalAlign": "middle", - "strokeColor": "#18181B", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "seed": 10315, - "version": 1, - "versionNonce": 20315, - "isDeleted": false, - "groupIds": [], - "boundElements": null, - "link": null, - "locked": false, - "containerId": null, - "lineHeight": 1.25 } ], "appState": { - "viewBackgroundColor": "#ffffff", + "viewBackgroundColor": "#FFFFFF", "gridSize": null }, "files": {} diff --git a/docs/cloud/images/v4-workspaces-light.svg b/docs/cloud/images/v4-workspaces-light.svg index 11820869..93ee09b1 100644 --- a/docs/cloud/images/v4-workspaces-light.svg +++ b/docs/cloud/images/v4-workspaces-light.svg @@ -1,60 +1,29 @@ - - One workspace shared across sessions - Two independent sessions read and write files in the same persistent workspace. - + + Sessions sharing a persistent workspace + Two independent sessions read and write files in one workspace. - - - + + + - - - - - - - - - - - Session A - research conversation - reads + writes files - - - - Session B - fresh conversation - reuses the same files - - - - - - Workspace - persistent files shared across sessions - - workspace_id - - - - people.csv - input - - - - script.py - reusable - - - - output.json - generated + + + + + + + + + + + + + + + SESSION A + SESSION B + WORKSPACE + FILES + diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 111b31fd..5fc16939 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -5,15 +5,8 @@ Source: https://docs.browser-use.com/cloud/quickstart -Browser Use Cloud gives you two ways to automate the web: - -Give it a goal and get the result. API V4 is built for hard, -high-accuracy work; API V2 trades accuracy for very low cost. -Control a managed browser directly with Playwright, Puppeteer, or another -remote CDP client. - - Both products run on the same browser infrastructure with stealth, - residential proxies, profiles, recordings, and live observability. +Give an agent a task and get the result. +Launch a managed browser and control it with Playwright or Puppeteer. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -22,9 +15,9 @@ export it: export BROWSER_USE_API_KEY=your_key ``` -## Install the Agent SDK +## Install the SDK -Skip this step if you use curl or only need direct Browser control. +Skip this step if you use curl. ```bash Python pip install browser-use-sdk @@ -33,9 +26,7 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## Run an agent - -Give API V4 a goal and get the result: +## Run a hosted agent ```python Python from browser_use_sdk.v4 import BrowserUse @@ -68,48 +59,27 @@ curl https://api.browser-use.com/api/v4/runs \ ## Control a browser -Create a standalone browser with API V4. Connect your CDP client to the -returned `cdpUrl`, then stop the browser with its `id`: +Launch a browser, connect to its CDP URL, then stop it: ```python Python -import os -import requests +from browser_use_sdk.v3 import BrowserUse -headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} -browser = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -print(browser["cdpUrl"]) +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -# After your CDP client is finished: -requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", - headers=headers, - json={"action": "stop"}, -).raise_for_status() +# When finished: +client.browsers.stop(browser.id) ``` ```typescript TypeScript -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; +import { BrowserUse } from "browser-use-sdk/v3"; +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); console.log(browser.cdpUrl); -// After your CDP client is finished: -await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), -}); +// When finished: +await client.browsers.stop(browser.id); ``` ```bash curl browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ @@ -117,18 +87,19 @@ browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ -H "Content-Type: application/json" \ -d '{"proxyCountryCode":"us"}') -echo "$browser" | jq -r .cdpUrl +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) -# After your CDP client is finished: +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: curl -X PATCH \ - "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + "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 or disconnecting your CDP client does not stop the managed browser. - Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + 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"}`. Give your coding agent the compact API V4 context. @@ -759,80 +730,41 @@ proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default. 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). -## Create, connect, and stop +## 1. Create a browser -Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it -with the browser session ID. +```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 import os -import requests from playwright.sync_api import sync_playwright -api_key = os.environ["BROWSER_USE_API_KEY"] -headers = {"X-Browser-Use-API-Key": api_key} -session = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -try: - with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(session["cdpUrl"]) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) -finally: - requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{session['id']}", - headers=headers, - json={"action": "stop"}, - ).raise_for_status() +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"; -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const session = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; - -try { - const browser = await chromium.connectOverCDP(session.cdpUrl); - const page = browser.contexts()[0].pages()[0]; - await page.goto("https://example.com"); - console.log(await page.title()); -} finally { - await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), - }); -} -``` -```bash curl -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) - -# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: -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"}' +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()); ``` ### Puppeteer @@ -840,15 +772,12 @@ curl -X PATCH \ ```typescript import puppeteer from "puppeteer-core"; -// Create `session` with API V4 as shown above. const browser = await puppeteer.connect({ - browserWSEndpoint: session.cdpUrl, + browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!, }); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); - -// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -856,10 +785,18 @@ console.log(await page.title()); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. - `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 - stop operation. Keep the returned browser session ID and call `PATCH - /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and - refunds unused browser time. +## 3. Stop the browser + +```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"}' +``` + + `browser.close()` and disconnecting CDP do not stop the managed browser. + Call `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. 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 diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 6891c9b2..4ee842de 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -42,7 +42,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run an agent or control a cloud browser directly. +- [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 diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index 18d92b0e..87fee3a4 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -1,27 +1,18 @@ --- title: Quick start -description: "Run an agent or control a cloud browser directly." +description: "Run a hosted agent or launch a cloud browser." icon: rocket --- -Browser Use Cloud gives you two ways to automate the web: - - - Give it a goal and get the result. API V4 is built for hard, - high-accuracy work; API V2 trades accuracy for very low cost. + + Give an agent a task and get the result. - - Control a managed browser directly with Playwright, Puppeteer, or another - remote CDP client. + + Launch a managed browser and control it with Playwright or Puppeteer. - - Both products run on the same browser infrastructure with stealth, - residential proxies, profiles, recordings, and live observability. - - Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -29,9 +20,9 @@ export it: export BROWSER_USE_API_KEY=your_key ``` -## Install the Agent SDK +## Install the SDK -Skip this step if you use curl or only need direct Browser control. +Skip this step if you use curl. ```bash Python @@ -42,9 +33,7 @@ npm install browser-use-sdk ``` -## Run an agent - -Give API V4 a goal and get the result: +## Run a hosted agent ```python Python @@ -79,49 +68,28 @@ curl https://api.browser-use.com/api/v4/runs \ ## Control a browser -Create a standalone browser with API V4. Connect your CDP client to the -returned `cdpUrl`, then stop the browser with its `id`: +Launch a browser, connect to its CDP URL, then stop it: ```python Python -import os -import requests - -headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} -browser = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -print(browser["cdpUrl"]) - -# After your CDP client is finished: -requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", - headers=headers, - json={"action": "stop"}, -).raise_for_status() +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 -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; +import { BrowserUse } from "browser-use-sdk/v3"; +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); console.log(browser.cdpUrl); -// After your CDP client is finished: -await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), -}); +// When finished: +await client.browsers.stop(browser.id); ``` ```bash curl browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ @@ -129,11 +97,12 @@ browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ -H "Content-Type: application/json" \ -d '{"proxyCountryCode":"us"}') -echo "$browser" | jq -r .cdpUrl +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) -# After your CDP client is finished: +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: curl -X PATCH \ - "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + "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"}' @@ -141,8 +110,8 @@ curl -X PATCH \ - Closing or disconnecting your CDP client does not stop the managed browser. - Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + 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"}`. diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 111b31fd..5fc16939 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -5,15 +5,8 @@ Source: https://docs.browser-use.com/cloud/quickstart -Browser Use Cloud gives you two ways to automate the web: - -Give it a goal and get the result. API V4 is built for hard, -high-accuracy work; API V2 trades accuracy for very low cost. -Control a managed browser directly with Playwright, Puppeteer, or another -remote CDP client. - - Both products run on the same browser infrastructure with stealth, - residential proxies, profiles, recordings, and live observability. +Give an agent a task and get the result. +Launch a managed browser and control it with Playwright or Puppeteer. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -22,9 +15,9 @@ export it: export BROWSER_USE_API_KEY=your_key ``` -## Install the Agent SDK +## Install the SDK -Skip this step if you use curl or only need direct Browser control. +Skip this step if you use curl. ```bash Python pip install browser-use-sdk @@ -33,9 +26,7 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## Run an agent - -Give API V4 a goal and get the result: +## Run a hosted agent ```python Python from browser_use_sdk.v4 import BrowserUse @@ -68,48 +59,27 @@ curl https://api.browser-use.com/api/v4/runs \ ## Control a browser -Create a standalone browser with API V4. Connect your CDP client to the -returned `cdpUrl`, then stop the browser with its `id`: +Launch a browser, connect to its CDP URL, then stop it: ```python Python -import os -import requests +from browser_use_sdk.v3 import BrowserUse -headers = {"X-Browser-Use-API-Key": os.environ["BROWSER_USE_API_KEY"]} -browser = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -print(browser["cdpUrl"]) +client = BrowserUse() +browser = client.browsers.create(proxy_country_code="us") +print(browser.cdp_url) -# After your CDP client is finished: -requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{browser['id']}", - headers=headers, - json={"action": "stop"}, -).raise_for_status() +# When finished: +client.browsers.stop(browser.id) ``` ```typescript TypeScript -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const browser = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; +import { BrowserUse } from "browser-use-sdk/v3"; +const client = new BrowserUse(); +const browser = await client.browsers.create({ proxyCountryCode: "us" }); console.log(browser.cdpUrl); -// After your CDP client is finished: -await fetch(`https://api.browser-use.com/api/v4/browsers/${browser.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), -}); +// When finished: +await client.browsers.stop(browser.id); ``` ```bash curl browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ @@ -117,18 +87,19 @@ browser=$(curl -sS https://api.browser-use.com/api/v4/browsers \ -H "Content-Type: application/json" \ -d '{"proxyCountryCode":"us"}') -echo "$browser" | jq -r .cdpUrl +export BROWSER_SESSION_ID=$(echo "$browser" | jq -r .id) +export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -r .cdpUrl) -# After your CDP client is finished: +# Connect Playwright or Puppeteer to $BROWSER_USE_CDP_URL, then stop: curl -X PATCH \ - "https://api.browser-use.com/api/v4/browsers/$(echo "$browser" | jq -r .id)" \ + "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 or disconnecting your CDP client does not stop the managed browser. - Keep its `id` and call `PATCH /api/v4/browsers/{id}` with + 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"}`. Give your coding agent the compact API V4 context. @@ -759,80 +730,41 @@ proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default. 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). -## Create, connect, and stop +## 1. Create a browser -Create a standalone browser with API V4, connect to its `cdpUrl`, then stop it -with the browser session ID. +```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 import os -import requests from playwright.sync_api import sync_playwright -api_key = os.environ["BROWSER_USE_API_KEY"] -headers = {"X-Browser-Use-API-Key": api_key} -session = requests.post( - "https://api.browser-use.com/api/v4/browsers", - headers=headers, - json={"proxyCountryCode": "us"}, -).json() - -try: - with sync_playwright() as p: - browser = p.chromium.connect_over_cdp(session["cdpUrl"]) - page = browser.contexts[0].pages[0] - page.goto("https://example.com") - print(page.title()) -finally: - requests.patch( - f"https://api.browser-use.com/api/v4/browsers/{session['id']}", - headers=headers, - json={"action": "stop"}, - ).raise_for_status() +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"; -const headers = { - "X-Browser-Use-API-Key": process.env.BROWSER_USE_API_KEY!, - "Content-Type": "application/json", -}; -const session = await fetch("https://api.browser-use.com/api/v4/browsers", { - method: "POST", - headers, - body: JSON.stringify({ proxyCountryCode: "us" }), -}).then((response) => response.json()) as { id: string; cdpUrl: string }; - -try { - const browser = await chromium.connectOverCDP(session.cdpUrl); - const page = browser.contexts()[0].pages()[0]; - await page.goto("https://example.com"); - console.log(await page.title()); -} finally { - await fetch(`https://api.browser-use.com/api/v4/browsers/${session.id}`, { - method: "PATCH", - headers, - body: JSON.stringify({ action: "stop" }), - }); -} -``` -```bash curl -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) - -# Connect your CDP client to $BROWSER_USE_CDP_URL, then stop the browser: -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"}' +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()); ``` ### Puppeteer @@ -840,15 +772,12 @@ curl -X PATCH \ ```typescript import puppeteer from "puppeteer-core"; -// Create `session` with API V4 as shown above. const browser = await puppeteer.connect({ - browserWSEndpoint: session.cdpUrl, + browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!, }); const [page] = await browser.pages(); await page.goto("https://example.com"); console.log(await page.title()); - -// Stop the managed browser with PATCH /api/v4/browsers/{session.id}. ``` ### Selenium @@ -856,10 +785,18 @@ console.log(await page.title()); Selenium's `debugger_address` only supports local `host:port` connections. Use Playwright or Puppeteer for remote CDP over WebSocket. - `client.close()`, `browser.close()`, and disconnecting CDP are not the API V4 - stop operation. Keep the returned browser session ID and call `PATCH - /api/v4/browsers/{id}` with `{"action":"stop"}`. This stops billing and - refunds unused browser time. +## 3. Stop the browser + +```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"}' +``` + + `browser.close()` and disconnecting CDP do not stop the managed browser. + Call `PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`. 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 diff --git a/docs/llms.txt b/docs/llms.txt index 6891c9b2..4ee842de 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -42,7 +42,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here ## Get Started -- [Quick start](https://docs.browser-use.com/cloud/quickstart): Run an agent or control a cloud browser directly. +- [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 From bd2f48827cd2c49733788052f1c1d38136c55f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:47:51 -0700 Subject: [PATCH 09/15] docs: add browser quickstart --- docs/cloud/browser/quickstart.mdx | 90 +++++++++++++++++++++++++++++++ docs/cloud/llms-full.txt | 79 ++++++++++++++++++++++++++- docs/cloud/llms.txt | 1 + docs/cloud/quickstart.mdx | 4 +- docs/docs.json | 1 + docs/llms-full.txt | 79 ++++++++++++++++++++++++++- docs/llms.txt | 1 + 7 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 docs/cloud/browser/quickstart.mdx diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx new file mode 100644 index 00000000..d713d44e --- /dev/null +++ b/docs/cloud/browser/quickstart.mdx @@ -0,0 +1,90 @@ +--- +title: Browser quickstart +sidebarTitle: Quick start +description: "Launch a managed browser and get its CDP URL." +icon: rocket +--- + +Browser Use gives you a managed Chromium browser with stealth, residential +proxies, live preview, and recording. + +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 +``` + + +## Get a CDP URL + + +```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/llms-full.txt b/docs/cloud/llms-full.txt index 5fc16939..b01a3c30 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -6,7 +6,7 @@ Source: https://docs.browser-use.com/cloud/quickstart Give an agent a task and get the result. -Launch a managed browser and control it with Playwright or Puppeteer. +Launch a managed browser and get its CDP URL. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -523,6 +523,83 @@ 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. +# Browser quickstart +Source: https://docs.browser-use.com/cloud/browser/quickstart + + +Browser Use gives you a managed Chromium browser with stealth, residential +proxies, live preview, and recording. + +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 +``` + +## Get a CDP URL + +```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. + # Stealth Source: https://docs.browser-use.com/cloud/browser/stealth diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index 4ee842de..f3dc68b6 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -56,6 +56,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser +- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a managed browser and get its CDP URL. - [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. diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index 87fee3a4..3b7853d3 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -8,8 +8,8 @@ icon: rocket Give an agent a task and get the result. - - Launch a managed browser and control it with Playwright or Puppeteer. + + Launch a managed browser and get its CDP URL. diff --git a/docs/docs.json b/docs/docs.json index 05e0130b..fba7d1e1 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -91,6 +91,7 @@ "group": "Browser", "icon": "globe", "pages": [ + "cloud/browser/quickstart", "cloud/browser/stealth", "cloud/browser/proxies", "cloud/browser/live-preview", diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 5fc16939..b01a3c30 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -6,7 +6,7 @@ Source: https://docs.browser-use.com/cloud/quickstart Give an agent a task and get the result. -Launch a managed browser and control it with Playwright or Puppeteer. +Launch a managed browser and get its CDP URL. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -523,6 +523,83 @@ 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. +# Browser quickstart +Source: https://docs.browser-use.com/cloud/browser/quickstart + + +Browser Use gives you a managed Chromium browser with stealth, residential +proxies, live preview, and recording. + +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 +``` + +## Get a CDP URL + +```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. + # Stealth Source: https://docs.browser-use.com/cloud/browser/stealth diff --git a/docs/llms.txt b/docs/llms.txt index 4ee842de..f3dc68b6 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -56,6 +56,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser +- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a managed browser and get its CDP URL. - [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. From 6db69a4aa3628e4caabc5cf139d1fc0c63c38c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:53:53 -0700 Subject: [PATCH 10/15] docs: explain browser connection URL --- docs/cloud/browser/quickstart.mdx | 13 +++++++++---- docs/cloud/llms-full.txt | 13 +++++++++---- docs/cloud/llms.txt | 2 +- docs/cloud/quickstart.mdx | 2 +- docs/llms-full.txt | 13 +++++++++---- docs/llms.txt | 2 +- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx index d713d44e..1ace4698 100644 --- a/docs/cloud/browser/quickstart.mdx +++ b/docs/cloud/browser/quickstart.mdx @@ -1,12 +1,17 @@ --- title: Browser quickstart sidebarTitle: Quick start -description: "Launch a managed browser and get its CDP URL." +description: "Launch a cloud browser and connect to it from your code." icon: rocket --- -Browser Use gives you a managed Chromium browser with stealth, residential -proxies, live preview, and recording. +Browser Use is managed browser infrastructure. We run a production-ready +Chromium browser for you with stealth, residential proxies, live preview, and +recording built in. + +When you launch a browser, you get a **CDP URL**: the connection address that +Playwright, Puppeteer, or another browser automation client uses to control the +browser remotely. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -28,7 +33,7 @@ npm install browser-use-sdk ``` -## Get a CDP URL +## Launch a browser ```python Python diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index b01a3c30..126643d1 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -6,7 +6,7 @@ Source: https://docs.browser-use.com/cloud/quickstart Give an agent a task and get the result. -Launch a managed browser and get its CDP URL. +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: @@ -527,8 +527,13 @@ for the complete response shape. Source: https://docs.browser-use.com/cloud/browser/quickstart -Browser Use gives you a managed Chromium browser with stealth, residential -proxies, live preview, and recording. +Browser Use is managed browser infrastructure. We run a production-ready +Chromium browser for you with stealth, residential proxies, live preview, and +recording built in. + +When you launch a browser, you get a **CDP URL**: the connection address that +Playwright, Puppeteer, or another browser automation client uses to control the +browser remotely. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -548,7 +553,7 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## Get a CDP URL +## Launch a browser ```python Python from browser_use_sdk.v3 import BrowserUse diff --git a/docs/cloud/llms.txt b/docs/cloud/llms.txt index f3dc68b6..3a2b3937 100644 --- a/docs/cloud/llms.txt +++ b/docs/cloud/llms.txt @@ -56,7 +56,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser -- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a managed browser and get its CDP URL. +- [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. diff --git a/docs/cloud/quickstart.mdx b/docs/cloud/quickstart.mdx index 3b7853d3..8590383d 100644 --- a/docs/cloud/quickstart.mdx +++ b/docs/cloud/quickstart.mdx @@ -9,7 +9,7 @@ icon: rocket Give an agent a task and get the result. - Launch a managed browser and get its CDP URL. + Launch a cloud browser and connect to it from your code. diff --git a/docs/llms-full.txt b/docs/llms-full.txt index b01a3c30..126643d1 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -6,7 +6,7 @@ Source: https://docs.browser-use.com/cloud/quickstart Give an agent a task and get the result. -Launch a managed browser and get its CDP URL. +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: @@ -527,8 +527,13 @@ for the complete response shape. Source: https://docs.browser-use.com/cloud/browser/quickstart -Browser Use gives you a managed Chromium browser with stealth, residential -proxies, live preview, and recording. +Browser Use is managed browser infrastructure. We run a production-ready +Chromium browser for you with stealth, residential proxies, live preview, and +recording built in. + +When you launch a browser, you get a **CDP URL**: the connection address that +Playwright, Puppeteer, or another browser automation client uses to control the +browser remotely. Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys&new=1) and export it: @@ -548,7 +553,7 @@ pip install browser-use-sdk npm install browser-use-sdk ``` -## Get a CDP URL +## Launch a browser ```python Python from browser_use_sdk.v3 import BrowserUse diff --git a/docs/llms.txt b/docs/llms.txt index f3dc68b6..3a2b3937 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -56,7 +56,7 @@ export BROWSER_USE_API_KEY=bu_your_key_here - [Observability](https://docs.browser-use.com/cloud/agent/observability): Poll ordered V4 events to monitor a run or build a custom UI. ## Browser -- [Browser quickstart](https://docs.browser-use.com/cloud/browser/quickstart): Launch a managed browser and get its CDP URL. +- [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. From fdc8b3bcc40a4cfeb148a6917b2663e914986516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:59:20 -0700 Subject: [PATCH 11/15] docs: illustrate proxies and browser profiles --- docs/cloud/browser/proxies.mdx | 21 +- docs/cloud/guides/authentication.mdx | 16 + .../images/browser-profile-dark.excalidraw | 410 ++++++++++++++++++ docs/cloud/images/browser-profile-dark.svg | 33 ++ .../images/browser-profile-light.excalidraw | 410 ++++++++++++++++++ docs/cloud/images/browser-profile-light.svg | 33 ++ .../images/browser-proxy-dark.excalidraw | 262 +++++++++++ docs/cloud/images/browser-proxy-dark.svg | 27 ++ .../images/browser-proxy-light.excalidraw | 262 +++++++++++ docs/cloud/images/browser-proxy-light.svg | 27 ++ docs/cloud/llms-full.txt | 37 +- docs/llms-full.txt | 37 +- 12 files changed, 1569 insertions(+), 6 deletions(-) create mode 100644 docs/cloud/images/browser-profile-dark.excalidraw create mode 100644 docs/cloud/images/browser-profile-dark.svg create mode 100644 docs/cloud/images/browser-profile-light.excalidraw create mode 100644 docs/cloud/images/browser-profile-light.svg create mode 100644 docs/cloud/images/browser-proxy-dark.excalidraw create mode 100644 docs/cloud/images/browser-proxy-dark.svg create mode 100644 docs/cloud/images/browser-proxy-light.excalidraw create mode 100644 docs/cloud/images/browser-proxy-light.svg diff --git a/docs/cloud/browser/proxies.mdx b/docs/cloud/browser/proxies.mdx index 9327e4bd..0acaae70 100644 --- a/docs/cloud/browser/proxies.mdx +++ b/docs/cloud/browser/proxies.mdx @@ -4,8 +4,25 @@ description: "Route API V4 agent runs through residential or custom proxies." icon: globe --- -A US residential proxy is enabled by default. Set `browser_settings` / -`browserSettings` when you create a V4 run to choose another country: +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 diff --git a/docs/cloud/guides/authentication.mdx b/docs/cloud/guides/authentication.mdx index d7ed70a7..072ac852 100644 --- a/docs/cloud/guides/authentication.mdx +++ b/docs/cloud/guides/authentication.mdx @@ -5,6 +5,22 @@ 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: diff --git a/docs/cloud/images/browser-profile-dark.excalidraw b/docs/cloud/images/browser-profile-dark.excalidraw new file mode 100644 index 00000000..053b0392 --- /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 00000000..6f7c08bc --- /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 00000000..ca5eb08d --- /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 00000000..6d206b8e --- /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 00000000..2ef7de16 --- /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 00000000..9409dd3c --- /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 00000000..571f6c4f --- /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 00000000..b6f22c7f --- /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/llms-full.txt b/docs/cloud/llms-full.txt index 126643d1..76716345 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -627,8 +627,25 @@ Residential proxies are enabled by default across 195+ countries. This makes bro Source: https://docs.browser-use.com/cloud/browser/proxies -A US residential proxy is enabled by default. Set `browser_settings` / -`browserSettings` when you create a V4 run to choose another country: +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 @@ -889,6 +906,22 @@ Source: https://docs.browser-use.com/cloud/guides/authentication 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: diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 126643d1..76716345 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -627,8 +627,25 @@ Residential proxies are enabled by default across 195+ countries. This makes bro Source: https://docs.browser-use.com/cloud/browser/proxies -A US residential proxy is enabled by default. Set `browser_settings` / -`browserSettings` when you create a V4 run to choose another country: +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 @@ -889,6 +906,22 @@ Source: https://docs.browser-use.com/cloud/guides/authentication 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: From d98d4c4dc9b336009c6c341572602bd6bded5545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:02:29 -0700 Subject: [PATCH 12/15] docs: illustrate browser CDP connection --- docs/cloud/browser/quickstart.mdx | 13 + docs/cloud/images/browser-cdp-dark.excalidraw | 294 ++++++++++++++++++ docs/cloud/images/browser-cdp-dark.svg | 28 ++ .../cloud/images/browser-cdp-light.excalidraw | 294 ++++++++++++++++++ docs/cloud/images/browser-cdp-light.svg | 28 ++ docs/cloud/llms-full.txt | 13 + docs/llms-full.txt | 13 + 7 files changed, 683 insertions(+) create mode 100644 docs/cloud/images/browser-cdp-dark.excalidraw create mode 100644 docs/cloud/images/browser-cdp-dark.svg create mode 100644 docs/cloud/images/browser-cdp-light.excalidraw create mode 100644 docs/cloud/images/browser-cdp-light.svg diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx index 1ace4698..6ecc6688 100644 --- a/docs/cloud/browser/quickstart.mdx +++ b/docs/cloud/browser/quickstart.mdx @@ -13,6 +13,19 @@ When you launch a browser, you get a **CDP URL**: the connection address that Playwright, Puppeteer, or another browser automation client uses to control the browser remotely. +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: diff --git a/docs/cloud/images/browser-cdp-dark.excalidraw b/docs/cloud/images/browser-cdp-dark.excalidraw new file mode 100644 index 00000000..cc55c238 --- /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 00000000..feeac977 --- /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 00000000..9b797ca1 --- /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 00000000..0f6ae71f --- /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/llms-full.txt b/docs/cloud/llms-full.txt index 76716345..014f04f8 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -535,6 +535,19 @@ When you launch a browser, you get a **CDP URL**: the connection address that Playwright, Puppeteer, or another browser automation client uses to control the browser remotely. +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: diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 76716345..014f04f8 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -535,6 +535,19 @@ When you launch a browser, you get a **CDP URL**: the connection address that Playwright, Puppeteer, or another browser automation client uses to control the browser remotely. +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: From afdc79ac84d96bb2931c6c1c24a7afd501eb5b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20=C5=BDuni=C4=8D?= <36313686+gregpr07@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:03:29 -0700 Subject: [PATCH 13/15] docs: tighten browser quickstart intro --- docs/cloud/browser/quickstart.mdx | 9 ++------- docs/cloud/llms-full.txt | 9 ++------- docs/llms-full.txt | 9 ++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx index 6ecc6688..b3dec5f2 100644 --- a/docs/cloud/browser/quickstart.mdx +++ b/docs/cloud/browser/quickstart.mdx @@ -5,13 +5,8 @@ description: "Launch a cloud browser and connect to it from your code." icon: rocket --- -Browser Use is managed browser infrastructure. We run a production-ready -Chromium browser for you with stealth, residential proxies, live preview, and -recording built in. - -When you launch a browser, you get a **CDP URL**: the connection address that -Playwright, Puppeteer, or another browser automation client uses to control the -browser remotely. +Every browser includes stealth, proxies, live preview, and recording. Its +**CDP URL** is the address your code uses to connect. Date: Sun, 26 Jul 2026 00:05:16 -0700 Subject: [PATCH 14/15] docs: define CDP URL precisely --- docs/cloud/browser/quickstart.mdx | 2 +- docs/cloud/llms-full.txt | 2 +- docs/llms-full.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/cloud/browser/quickstart.mdx b/docs/cloud/browser/quickstart.mdx index b3dec5f2..11beb3b6 100644 --- a/docs/cloud/browser/quickstart.mdx +++ b/docs/cloud/browser/quickstart.mdx @@ -6,7 +6,7 @@ icon: rocket --- Every browser includes stealth, proxies, live preview, and recording. Its -**CDP URL** is the address your code uses to connect. +**CDP URL** is a WebSocket endpoint for remotely controlling Chrome. Date: Sun, 26 Jul 2026 11:45:10 -0700 Subject: [PATCH 15/15] docs: address API v4 review feedback --- docs/cloud/agent/human-in-the-loop.mdx | 17 +++++++++ docs/cloud/agent/models.mdx | 6 +++ docs/cloud/agent/observability.mdx | 2 +- docs/cloud/agent/structured-output.mdx | 6 +++ docs/cloud/agent/workspaces.mdx | 6 +++ docs/cloud/faq.mdx | 9 +++-- docs/cloud/guides/2fa.mdx | 5 ++- docs/cloud/llms-full.txt | 51 +++++++++++++++++++++++--- docs/llms-full.txt | 51 +++++++++++++++++++++++--- 9 files changed, 135 insertions(+), 18 deletions(-) diff --git a/docs/cloud/agent/human-in-the-loop.mdx b/docs/cloud/agent/human-in-the-loop.mdx index 3ff71354..816f4047 100644 --- a/docs/cloud/agent/human-in-the-loop.mdx +++ b/docs/cloud/agent/human-in-the-loop.mdx @@ -9,6 +9,14 @@ After a run stops, get its `live_view_url` from the `browser.ready` event: ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" +) +run = client.runs.wait_for_completion(run.id) + events = client.runs.events(run.id, limit=100) ready = next( event for event in events.events @@ -23,6 +31,15 @@ next_run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); +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, }); diff --git a/docs/cloud/agent/models.mdx b/docs/cloud/agent/models.mdx index 8971118a..cbcb02e1 100644 --- a/docs/cloud/agent/models.mdx +++ b/docs/cloud/agent/models.mdx @@ -32,12 +32,18 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() run = client.runs.create( "Compare three project-management tools", model="grok-4.5", ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const run = await client.runs.create({ task: "Compare three project-management tools", model: "grok-4.5", diff --git a/docs/cloud/agent/observability.mdx b/docs/cloud/agent/observability.mdx index fdd84ba3..1a26eb22 100644 --- a/docs/cloud/agent/observability.mdx +++ b/docs/cloud/agent/observability.mdx @@ -15,7 +15,7 @@ while True: page = client.runs.events(run.id, after=after) for event in page.events: print(event.type, event.data) - after = page.next_after or after + 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"}: diff --git a/docs/cloud/agent/structured-output.mdx b/docs/cloud/agent/structured-output.mdx index 2228353d..dc992fac 100644 --- a/docs/cloud/agent/structured-output.mdx +++ b/docs/cloud/agent/structured-output.mdx @@ -9,8 +9,11 @@ client-side: ```python Python +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel +client = BrowserUse() + class Story(BaseModel): title: str points: int @@ -22,8 +25,11 @@ run = client.runs.wait_for_completion(run.id) story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; +const client = new BrowserUse(); + const Story = z.object({ title: z.string(), points: z.number(), diff --git a/docs/cloud/agent/workspaces.mdx b/docs/cloud/agent/workspaces.mdx index b75a7711..cc0cda7c 100644 --- a/docs/cloud/agent/workspaces.mdx +++ b/docs/cloud/agent/workspaces.mdx @@ -24,6 +24,9 @@ different sessions. Use it for inputs, scripts, and generated files. ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() workspace = client.workspaces.create(name="research") uploaded = client.workspaces.upload(workspace.id, "people.csv") @@ -34,6 +37,9 @@ run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const workspace = await client.workspaces.create({ name: "research", }); diff --git a/docs/cloud/faq.mdx b/docs/cloud/faq.mdx index 2aef8ab6..50d26c1f 100644 --- a/docs/cloud/faq.mdx +++ b/docs/cloud/faq.mdx @@ -18,9 +18,12 @@ See [Models](/cloud/agent/models) for the complete V4 picker and pricing. The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -created = await client.runs.create("Go to example.com") -await client.runs.wait_for_completion(created.id) -events = await client.runs.events(created.id, limit=100) +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"]) ``` diff --git a/docs/cloud/guides/2fa.mdx b/docs/cloud/guides/2fa.mdx index 116268c3..f75513da 100644 --- a/docs/cloud/guides/2fa.mdx +++ b/docs/cloud/guides/2fa.mdx @@ -34,8 +34,9 @@ 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, open its `live_view_url`, and have -the user enter the code. Then continue with the same session: +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 diff --git a/docs/cloud/llms-full.txt b/docs/cloud/llms-full.txt index 2d2f8883..e8c4118a 100644 --- a/docs/cloud/llms-full.txt +++ b/docs/cloud/llms-full.txt @@ -202,12 +202,18 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c directly for that model. ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() run = client.runs.create( "Compare three project-management tools", model="grok-4.5", ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const run = await client.runs.create({ task: "Compare three project-management tools", model: "grok-4.5", @@ -235,8 +241,11 @@ V4 returns `run.result` as a string. Ask for JSON only, then validate it client-side: ```python Python +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel +client = BrowserUse() + class Story(BaseModel): title: str points: int @@ -248,8 +257,11 @@ run = client.runs.wait_for_completion(run.id) story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; +const client = new BrowserUse(); + const Story = z.object({ title: z.string(), points: z.number(), @@ -343,6 +355,9 @@ different sessions. Use it for inputs, scripts, and generated files. ## Upload and attach a file ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() workspace = client.workspaces.create(name="research") uploaded = client.workspaces.upload(workspace.id, "people.csv") @@ -353,6 +368,9 @@ run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const workspace = await client.workspaces.create({ name: "research", }); @@ -450,6 +468,14 @@ 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.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" +) +run = client.runs.wait_for_completion(run.id) + events = client.runs.events(run.id, limit=100) ready = next( event for event in events.events @@ -464,6 +490,15 @@ next_run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); +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, }); @@ -497,7 +532,7 @@ while True: page = client.runs.events(run.id, after=after) for event in page.events: print(event.type, event.data) - after = page.next_after or after + 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"}: @@ -1043,8 +1078,9 @@ 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, open its `live_view_url`, and have -the user enter the code. Then continue with the same session: +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 first = client.runs.create( @@ -1584,9 +1620,12 @@ See [Models](https://docs.browser-use.com/cloud/agent/models) for the complete V The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -created = await client.runs.create("Go to example.com") -await client.runs.wait_for_completion(created.id) -events = await client.runs.events(created.id, limit=100) +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"]) ``` diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 2d2f8883..e8c4118a 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -202,12 +202,18 @@ proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.c directly for that model. ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() run = client.runs.create( "Compare three project-management tools", model="grok-4.5", ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const run = await client.runs.create({ task: "Compare three project-management tools", model: "grok-4.5", @@ -235,8 +241,11 @@ V4 returns `run.result` as a string. Ask for JSON only, then validate it client-side: ```python Python +from browser_use_sdk.v4 import BrowserUse from pydantic import BaseModel +client = BrowserUse() + class Story(BaseModel): title: str points: int @@ -248,8 +257,11 @@ run = client.runs.wait_for_completion(run.id) story = Story.model_validate_json(run.result or "{}") ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; import { z } from "zod"; +const client = new BrowserUse(); + const Story = z.object({ title: z.string(), points: z.number(), @@ -343,6 +355,9 @@ different sessions. Use it for inputs, scripts, and generated files. ## Upload and attach a file ```python Python +from browser_use_sdk.v4 import BrowserUse + +client = BrowserUse() workspace = client.workspaces.create(name="research") uploaded = client.workspaces.upload(workspace.id, "people.csv") @@ -353,6 +368,9 @@ run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); const workspace = await client.workspaces.create({ name: "research", }); @@ -450,6 +468,14 @@ 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.v4 import BrowserUse + +client = BrowserUse() +run = client.runs.create( + "Open the login page and stop for human review" +) +run = client.runs.wait_for_completion(run.id) + events = client.runs.events(run.id, limit=100) ready = next( event for event in events.events @@ -464,6 +490,15 @@ next_run = client.runs.create( ) ``` ```typescript TypeScript +import { BrowserUse } from "browser-use-sdk/v4"; + +const client = new BrowserUse(); +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, }); @@ -497,7 +532,7 @@ while True: page = client.runs.events(run.id, after=after) for event in page.events: print(event.type, event.data) - after = page.next_after or after + 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"}: @@ -1043,8 +1078,9 @@ 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, open its `live_view_url`, and have -the user enter the code. Then continue with the same session: +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 first = client.runs.create( @@ -1584,9 +1620,12 @@ See [Models](https://docs.browser-use.com/cloud/agent/models) for the complete V The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser. ```python -created = await client.runs.create("Go to example.com") -await client.runs.wait_for_completion(created.id) -events = await client.runs.events(created.id, limit=100) +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"]) ```