> ## Agent Instructions
> Use https://docs.browser-use.com/llms.txt and its linked .md pages for current documentation. The managed full bundle is https://docs.browser-use.com/.well-known/llms-full.txt and can be cached for up to 24 hours. Do not use the obsolete /cloud/llms*.txt or /open-source/llms*.txt static exports.
> Choose Cloud API V4 for new agent integrations; V2 is the lower-cost option for simple tasks. Keep V3 examples explicitly versioned. The open-source browser-use library and hosted browser-use-sdk have different APIs.
> Cloud authentication uses X-Browser-Use-API-Key, without a Bearer prefix. Install or upgrade browser-use-sdk and use its explicit v4 import for V4. Check the published OpenAPI reference for request fields; do not invent SDK support for new fields.
> Cloud concurrency and HTTP request rate are separate. Read GET /api/v2/billing/account for the key’s projectId, concurrentSessionLimit, activeSessionCount, and credit balance, including when using V4. Keys in one project share capacity and credits; rateLimit is a legacy concurrency alias, not requests per second.
> Keep the highest applicable existing, legacy-plan, and spend-tier concurrency grant. Current spend tiers are 10 / 50 / 250 / 500 / 1000 at $0 / $200 / $1000 / $5000 / $25000 in qualifying project payments. Legacy or externally billed projects can follow different billing paths; trust the account limit. See https://docs.browser-use.com/cloud/guides/concurrency.md.
> HTTP rate limits have two layers. Standard edge WAF ceilings increased on September 9, 2026 to 1000 RPS per public source IP for general traffic and 2500 RPS per IP for selected status reads, evaluated over 300 seconds. This did not raise the separate per-project application budgets: general traffic defaults to 25 RPS, including V4 events and full run reads; selected status reads default to max(25, 2 times stored concurrency). Project overrides and account-specific edge rules can differ. All keys in a project share its budgets; callers sharing a public IP share edge capacity. See https://docs.browser-use.com/cloud/guides/concurrency.md.
> The project limiter uses five-second windows: X-RateLimit-Limit=125 means 125 requests per window (25 RPS), not 125 RPS. Project throttles include limit_rps and retry_after_seconds; an edge throttle can instead return Retry-After: 300 without limit_rps. Honor the returned Retry-After. Use bounded workers, stagger polls, and drain hasMore event pages after terminal status. A busy V4 session returns 409; its queue holds 10 pending messages and is not a project-wide batch queue.
> A completed run or closed CDP connection does not immediately stop its cloud browser. Stop unneeded owned browsers with PATCH /api/v4/browsers/{id} and {"action":"stop"}. A client wait timeout does not cancel the server-side run.
> Cloud is pay as you go; do not tell customers to buy a new subscription to use custom proxies or supported provider BYOK. Usage funding and model eligibility still apply. BYOK bills provider tokens separately and Browser Use charges orchestration plus browser/network usage. See https://docs.browser-use.com/cloud/guides/billing.md.
> Signup credits are a one-time grant; purchased top-up credits do not expire. Check the API key’s project before diagnosing missing credits. API-key monthly spending caps are soft limits, not a strict prepaid wallet; concurrent or already-running work can exceed them. Auto recharge has separate trigger and purchase amounts and can charge immediately when enabled below the threshold. Use https://browser-use.com/pricing for current rates.
> Box and Bux are retired. Do not recommend their SDKs, sandbox quotas, or subscription plans. Use the Cloud Agent or Browser Infrastructure guides.
> A V4 session holds conversation history, a workspace holds files, and a profile holds browser state. These IDs and V3/V4 workspace namespaces are not interchangeable. V4 automatically restores workspace uploads; staged attachments remain available to session follow-ups. Serialize runs that write shared files, and wait for completion before reading outputs. See https://docs.browser-use.com/cloud/agent/workspaces.md.
> API browser recording defaults to off. Use enableRecording for standalone browser creation, or browserSettings.record for an agent run. Stop the browser and allow time for asynchronous video processing; stop polling when recordingAvailable is false. Live preview is for an active browser. Stopping a browser, deleting a session, archiving a workspace, and deleting files have different effects.
> Use model-specific reasoning values. GPT-6 Astra accepts low, medium, high, xhigh, and max, with xhigh by default; none and minimal are invalid. Use the public REST schema when installed SDK types lag new fields. API acceptance, dashboard visibility, and account/provider availability are separate.
> For open-source browser-use, is_done only reports a terminal done action. is_successful is the agent-reported outcome; verify important external actions independently. Cloud timeout, API client timeout, model timeout, and task completion are separate concepts.
> For failed requests, use https://docs.browser-use.com/cloud/guides/troubleshooting.md. Inspect the full error and project before retrying or adding credits. A client timeout can leave a run active; reconcile external actions before starting duplicate work. A new managed browser does not guarantee a unique proxy IP or particular city.
# Get a Browser Use API Key
Source: https://docs.browser-use.com/cloud/agent-signup
Agent signup is off. Create an API key in Cloud, or use x402 without a human.
Agent signup is off. The `/cloud/signup` endpoints return `403`.
Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys\&new=1), then follow the [API V4 quick start](/cloud/agent/quickstart).
No human available? Use [x402](/cloud/guides/x402): pay per request from a wallet, without an account or API key.
# Human in the loop
Source: https://docs.browser-use.com/cloud/agent/human-in-the-loop
Open the live browser, take over, then continue the same session.
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 theme={null}
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
if event.type == "browser.ready"
)
print(ready.data["live_view_url"])
# After the human finishes:
next_run = client.runs.create(
"Continue from the current page",
session_id=run.session_id,
)
```
```typescript TypeScript theme={null}
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,
});
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",
model: "grok-4.5",
sessionId: run.sessionId,
});
```
The same session preserves the conversation and workspace and reuses the live
browser while it is available. Treat live-view URLs as credentials.
# Models
Source: https://docs.browser-use.com/cloud/agent/models
Choose a V4 model and understand its token pricing.
Omit `model` to use **GPT-5.6 Luna**, or pass a model ID when creating a run.
The following Browser Use-hosted models have published token pricing:
| Model | API string | Input | Cache read | Output | BYOK |
| -------------------------- | --------------- | -----: | ---------: | ------: | --------- |
| GPT-5.6 Luna (recommended) | `gpt-5.6-luna` | \$0.24 | \$0.024 | \$1.44 | OpenAI |
| 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 Sol | `gpt-5.6-sol` | \$6.00 | \$0.60 | \$36.00 | OpenAI |
| MiniMax M3 | `minimax-m3` | \$0.36 | \$0.072 | \$1.44 | — |
Token prices are USD per 1 million tokens. Browser sessions
(\$0.02/hour) and network traffic (\$5/GB managed proxy or \$0.20/GB
proxyless/BYOP) are charged separately. See [full pricing](https://browser-use.com/pricing).
See [Thinking levels](/cloud/agent/thinking-levels) for V4 `modelParams` and
the normalized `thinkingLevel` values available in V2 and V3.
We recommend **GPT-5.6 Luna** for most tasks. It combines near-Opus benchmark
performance with the lowest token price and fastest median response time in
current V4 production traffic.
## All supported V4 model IDs
The V4 request schema accepts the following model IDs. The Cloud dashboard may
show a smaller curated set; availability can also depend on your account's
rollout and configured provider key. An accepted ID does not guarantee access
to every upstream provider. Experimental models are not recommended as a
production default. MiniMax M3 remains an API option even where it is absent
from the dashboard picker.
| Provider | Model IDs | BYOK provider |
| ----------------------- | ------------------------------------------------------------------------------------------ | ------------- |
| Anthropic | `claude-opus-4.7`, `claude-opus-4.8`, `claude-opus-5`, `claude-fable-5`, `claude-sonnet-5` | Anthropic |
| OpenAI | `gpt-5.5`, `gpt-5.6`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-6-astra` | OpenAI |
| Google | `gemini-3-flash`, `gemini-3.1-pro`, `gemini-3.5-flash`, `gemini-3.6-flash` | Google |
| xAI | `grok-4.5`, `grok-4.6` | — |
| Z.ai | `glm-5.2`, `glm-5.3-flash` | — |
| DeepSeek (experimental) | `deepseek-v4-flash-vision` | — |
| Moonshot AI | `kimi-k3` | — |
| MiniMax | `minimax-m3` | — |
See [Thinking levels](/cloud/agent/thinking-levels) for the reasoning controls
accepted by each model. Not every model accepts `modelParams`.
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
run = client.runs.create(
"Compare three project-management tools",
model="gpt-5.6-luna",
)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const run = await client.runs.create({
task: "Compare three project-management tools",
model: "gpt-5.6-luna",
});
```
```bash curl theme={null}
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":"gpt-5.6-luna"}'
```
## Bring your own key
BYOK is available to pay-as-you-go customers; no recurring subscription is required. After adding credits, 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. Models from other providers use Browser Use-managed
keys.
# Observability
Source: https://docs.browser-use.com/cloud/agent/observability
Poll ordered V4 events to monitor a run or build a custom UI.
If you only need the result, use `runs.wait_for_completion()` in Python or
`runs.waitForCompletion()` in TypeScript. It polls the lightweight status
endpoint instead of downloading every event.
For a custom UI, poll `runs.events()` with the previous cursor. Read status
**before** fetching events so that the final fetch happens after you observe a
terminal state, and drain all `hasMore` pages before stopping:
```python Python theme={null}
import time
after = None
while True:
status = client.runs.status(run.id).status.value
page = client.runs.events(run.id, after=after)
for event in page.events:
print(event.type, event.data)
after = page.next_after if page.next_after is not None else after
if status in {"completed", "failed", "cancelled"} and not page.has_more:
break
time.sleep(2)
```
```typescript TypeScript theme={null}
let after: number | undefined;
while (true) {
const { status } = await client.runs.status(run.id);
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;
if (["completed", "failed", "cancelled"].includes(status) && !page.hasMore) break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
```
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.
This two-second interval is an example for one stream, not a safe default for
an unbounded batch. Event reads share the project's general request bucket.
Use a shared rate limiter and stagger polling across runs. Keep the cursor
when backing off after a 429, and resume from it rather than rereading history.
See [Concurrency and rate limits](/cloud/guides/concurrency) for capacity,
request budgets, retries, and the separate per-session message queue.
# Run a task
Source: https://docs.browser-use.com/cloud/agent/quickstart
Give a high-accuracy browser agent a goal and get the result.
Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys\&new=1) and export it:
```bash theme={null}
export BROWSER_USE_API_KEY=your_key
```
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
with BrowserUse() as client:
run = client.runs.create("Find the top Hacker News story")
result = client.runs.wait_for_completion(run.id)
print(result.result)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const run = await client.runs.create({
task: "Find the top Hacker News story",
});
const result = await client.runs.waitForCompletion(run.id);
console.log(result.result);
```
```bash curl theme={null}
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"}'
```
Install the latest SDK with `pip install --upgrade browser-use-sdk` or
`npm install browser-use-sdk@latest`. Curl needs no installation.
Every new run implicitly creates a [session](/cloud/agent/sessions) for its
conversation and live browser, plus a [workspace](/cloud/agent/workspaces) for
persistent files.
Give the compact context file to your coding agent.
# Rerunnable scripts
Source: https://docs.browser-use.com/cloud/agent/scripts
Save a Browser Use task as a rerunnable script for repeated live data extraction and self-healing runs.
Teach a Cloud agent a repeated browser task once. It can save the working code
in its [workspace](/cloud/agent/workspaces). Later runs reuse that script,
fetch fresh data from the live site, and repair the code if the site changed.
This pattern works well for repeated data extraction, checks, and reports. The
example below collects the top five Hacker News stories. The first run writes
and tests the script. Every later run sends a new request to the live page.
The file stays. The running process does not. Each later run starts an agent
again, but it can reuse the code instead of rebuilding the browser workflow.
## Save the working script
Create one workspace, then ask the first run to do the job and save a tested
script:
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
workspace = client.workspaces.create(name="live-hn-data")
first = client.runs.create(
"""
Open https://news.ycombinator.com/ and return the top five story titles and
URLs as JSON.
Save a script at scripts/extract_top_stories.py that collects the same
fields. The script must send a fresh request to the live page every time it
runs. Do not hard-code story titles, save the page HTML, or read an old
result. Print JSON with source_url set to https://news.ycombinator.com/ and
include fetched_at, source_sha256, and stories.
Run the script twice and save the outputs as proof/run-1.json and
proof/run-2.json. Confirm both outputs have a fresh fetched_at value and
five stories. Add scripts/README.md with the command to run it again.
""",
model="grok-4.5",
workspace_id=workspace.id,
)
client.runs.wait_for_completion(first.id)
saved = client.workspaces.files(workspace.id, prefix="scripts/")
print([file.path for file in saved.files])
print(f"Keep this workspace ID: {workspace.id}")
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const workspace = await client.workspaces.create({ name: "live-hn-data" });
const first = await client.runs.create({
task: `
Open https://news.ycombinator.com/ and return the top five story titles and
URLs as JSON.
Save a script at scripts/extract_top_stories.py that collects the same
fields. The script must send a fresh request to the live page every time it
runs. Do not hard-code story titles, save the page HTML, or read an old
result. Print JSON with source_url set to https://news.ycombinator.com/ and
include fetched_at, source_sha256, and stories.
Run the script twice and save the outputs as proof/run-1.json and
proof/run-2.json. Confirm both outputs have a fresh fetched_at value and
five stories. Add scripts/README.md with the command to run it again.
`,
model: "grok-4.5",
workspaceId: workspace.id,
});
await client.runs.waitForCompletion(first.id);
const saved = await client.workspaces.files(workspace.id, {
prefix: "scripts/",
});
console.log(saved.files.map((file) => file.path));
console.log(`Keep this workspace ID: ${workspace.id}`);
```
## Rerun it later
Copy the workspace ID from the first process. Pass that ID without a session ID
to start a new conversation with the saved files:
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
workspace_id = "your-workspace-id"
later = client.runs.create(
"""
Run python scripts/extract_top_stories.py and return its JSON. Check that
fetched_at is current, source_url is https://news.ycombinator.com/, and it
has five stories. If the script fails or the result is malformed, inspect
the live page and repair the saved script before returning the new result.
""",
model="grok-4.5",
workspace_id=workspace_id,
)
result = client.runs.wait_for_completion(later.id)
print(result.result)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const workspaceId = "your-workspace-id";
const later = await client.runs.create({
task: `
Run python scripts/extract_top_stories.py and return its JSON. Check that
fetched_at is current, source_url is https://news.ycombinator.com/, and it
has five stories. If the script fails or the result is malformed, inspect
the live page and repair the saved script before returning the new result.
`,
model: "grok-4.5",
workspaceId,
});
const result = await client.runs.waitForCompletion(later.id);
console.log(result.result);
```
## Live data, not a cached answer
The saved script must request the source page on every execution. `fetched_at`
shows when the request ran, and `source_sha256` identifies the response body.
The data can stay the same between two runs when the page has not changed. The
script still fetched it again.
Do not put old output, page HTML, or fixed values inside the script. Save output
files only as optional history or proof.
## Can rerunnable tasks be cheaper at scale?
Reusing code can remove browser steps and model work that would otherwise be
repeated on every extraction. Every later run still starts a model and uses
tokens, so the savings depend on the task, site, and model. Compare the cost and
duration of the first run with later runs in your own project before making a
savings claim.
## Workspace or session?
* Pass `workspace_id` / `workspaceId` for a new conversation with the same
files. The new run gets a new browser.
* Pass `session_id` / `sessionId` to continue the old conversation and its
workspace. Cloud can also reuse that session's browser while it is still
alive.
A saved script can break when a site changes. Ask the agent to check the live
result and self-heal the script instead of trusting stale output.
# Sessions
Source: https://docs.browser-use.com/cloud/agent/sessions
Continue one conversation across multiple V4 runs.
A **session** holds the agent's conversation and can reuse its live browser.
One session ID can contain multiple runs. Every run creates a session implicitly
unless you pass an existing session ID.
Pass `session_id` / `sessionId` to continue:
```python Python theme={null}
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 theme={null}
const first = await client.runs.create({
task: "Open Hacker News",
model: "grok-4.5",
});
await client.runs.waitForCompletion(first.id);
const followUp = await client.runs.create({
task: "Now summarize the top story",
model: "grok-4.5",
sessionId: first.sessionId,
});
const result = await client.runs.waitForCompletion(followUp.id);
console.log(result.result);
```
Omit the session ID for a new conversation. Pass only a [workspace
ID](/cloud/agent/workspaces) when you want a fresh conversation that keeps the
same files.
# Structured output
Source: https://docs.browser-use.com/cloud/agent/structured-output
Ask for JSON and validate the V4 result in your application.
V4 returns `run.result` as a string. Ask for JSON only, then validate it
client-side:
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
from pydantic import BaseModel
client = BrowserUse()
class Story(BaseModel):
title: str
points: int
run = client.runs.create(
'Find the top HN story. Return only {"title":"...","points":0}.'
)
run = client.runs.wait_for_completion(run.id)
story = Story.model_validate_json(run.result or "{}")
```
```typescript TypeScript theme={null}
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(),
});
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 ?? "{}"));
```
V4 does not accept `output_schema` / `outputSchema`. Handle validation errors
and retry with a [session follow-up](/cloud/agent/sessions) when needed.
# Thinking levels
Source: https://docs.browser-use.com/cloud/agent/thinking-levels
Configure model reasoning depth across API V2, V3, and V4.
Browser Use exposes reasoning controls in two forms:
* **API V4** uses provider-native values inside `modelParams`.
* **API V3 and V2** use the provider-neutral `thinkingLevel` field with
`disabled`, `low`, `medium`, or `high`.
See [Models](/cloud/agent/models) for the complete V4 model list, recommended
model, token pricing, and BYOK routes.
## API V4 model parameters
V4 forwards an allow-listed `modelParams` object to the selected provider. The
field names and accepted values therefore differ by model family.
| Model strings | Path | Accepted values |
| ------------------------------------------------------------------------ | ------------------------------ | ----------------------------------------------- |
| `gpt-6-astra` | `reasoning.effort` | `low`, `medium`, `high`, `xhigh`, `max` |
| `gpt-5.5` | `reasoning.effort` | `none`, `low`, `medium`, `high`, `xhigh` |
| `gpt-5.6`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` | `reasoning.effort` | `none`, `low`, `medium`, `high`, `xhigh`, `max` |
| `claude-opus-4.7`, `claude-opus-4.8`, `claude-opus-5`, `claude-sonnet-5` | `output_config.effort` | `low`, `medium`, `high`, `xhigh`, `max` |
| Same Claude models | `thinking.type` | `adaptive`, `disabled` |
| Same Claude models | `thinking.display` | `omitted`, `summarized` |
| `gemini-3-flash`, `gemini-3.5-flash`, `gemini-3.6-flash` | `thinkingConfig.thinkingLevel` | `minimal`, `low`, `medium`, `high` |
| `gemini-3.1-pro` | `thinkingConfig.thinkingLevel` | `low`, `medium`, `high` |
`glm-5.2`, `glm-5.3-flash`, `grok-4.5`, `grok-4.6`,
`deepseek-v4-flash-vision`, `kimi-k3`, `minimax-m3`, and `claude-fable-5`
do not currently accept V4 `modelParams`.
Omitting `modelParams` applies Browser Use's defaults for that V4 model. For
example, `gpt-5.6-luna` and `gpt-6-astra` default to `reasoning.effort: xhigh`.
Astra does not accept `none` or `minimal`. Send an empty
object (`"modelParams": {}`) to opt out and use the provider's defaults.
### V4 examples
```bash OpenAI theme={null}
curl -X POST 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 project-management tools",
"model": "gpt-5.6-luna",
"modelParams": {"reasoning": {"effort": "high"}}
}'
```
```bash Anthropic theme={null}
curl -X POST 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 project-management tools",
"model": "claude-opus-5",
"modelParams": {"output_config": {"effort": "high"}}
}'
```
```bash Google theme={null}
curl -X POST 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 project-management tools",
"model": "gemini-3.6-flash",
"modelParams": {"thinkingConfig": {"thinkingLevel": "high"}}
}'
```
## API V3 support
V3 accepts the normalized REST field `thinkingLevel`. Legacy aliases are
normalized before validation: `bu-mini` maps to `gemini-3-flash`, `bu-max`
maps to `claude-sonnet-5`, and `bu-ultra` maps to `claude-opus-4.6`.
| Model strings | `disabled` | `low` | `medium` | `high` |
| ----------------------------------------------------------------------------------------------------------------------------------------- | :--------: | :---: | :------: | :----: |
| `bu-mini`, `gemini-3-flash`, `gemini-3.5-flash` | Yes | Yes | Yes | Yes |
| `bu-max`, `bu-ultra`, `claude-sonnet-4.6`, `claude-opus-4.6`, `claude-opus-4.7`, `claude-sonnet-5`, `claude-opus-4.8`, `claude-haiku-4.5` | Yes | Yes | Yes | Yes |
| `gpt-5.2`, `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` | Yes | Yes | Yes | Yes |
| `gpt-5-mini` | — | Yes | Yes | Yes |
| `gemini-3-pro`, `gemini-3.1-pro` | — | Yes | — | Yes |
| `glm-5.2` | Yes | — | — | Yes |
| `minimax-m3` | — | — | — | — |
For a follow-up task sent to an existing V3 session:
* Omit `thinkingLevel` to retain the session's current setting.
* Send a supported value to update the setting for the next task and later
follow-ups.
* Send `"thinkingLevel": null` to clear it and return to provider defaults.
The requested value is validated against the existing session's model, not
the request model default.
## API V2 support
V2 also accepts the normalized REST field `thinkingLevel`, but it has two
additional limitations. It cannot configure GLM thinking, and it cannot enable
fixed-budget thinking on older Claude 4/4.5 models because the legacy worker
cannot safely replay their thinking blocks. Those Claude models still accept
`disabled`.
| Model strings | `disabled` | `low` | `medium` | `high` |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------: | :---: | :------: | :----: |
| `browser-use-llm`, `browser-use-2.0`, `gemini-2.5-flash`, `gemini-3-flash-preview`, `gemini-3.5-flash`, `gemini-flash-latest`, `gemini-flash-lite-latest` | Yes | Yes | Yes | Yes |
| `gemini-2.5-pro` | — | Yes | Yes | Yes |
| `gemini-3-pro-preview`, `gemini-3.1-pro-preview` | — | Yes | — | Yes |
| `o3`, `o4-mini` | — | Yes | Yes | Yes |
| `gpt-5.5`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` | Yes | Yes | Yes | Yes |
| `claude-sonnet-5`, `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5` | Yes | Yes | Yes | Yes |
| `claude-sonnet-4-20250514`, `claude-sonnet-4-5-20250929`, `claude-opus-4-5-20251101` | Yes | — | — | — |
| `gpt-4.1`, `gpt-4.1-mini`, `glm-5.2`, `minimax-m3`, `llama-4-maverick-17b-128e-instruct`, `claude-3-7-sonnet-20250219` | — | — | — | — |
V2's existing `thinking` boolean controls legacy agent behavior. It does not
select a provider reasoning depth and does not replace `thinkingLevel`.
## Provider mappings for V2 and V3
Browser Use validates the normalized V2/V3 value before dispatch, then maps it
to the provider's native control.
| Provider/model family | Mapping |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gemini 3 Flash | `disabled` becomes provider level `minimal`; `low`, `medium`, and `high` map directly. |
| Gemini 3.1 Pro | Supports only provider levels `low` and `high`. Legacy Gemini 3 Pro names route here. |
| Gemini 2.5 | Uses thinking budgets of 0, 1,024, 4,096, and 8,192 tokens. Gemini 2.5 Pro cannot use the zero-token budget. |
| Claude 4.6+ and Claude 5 | V3 uses adaptive thinking with the requested effort. V2 sends the effort without replaying thinking blocks. `disabled` turns thinking off where supported. |
| Earlier supported Claude 4/4.5 | Uses fixed budgets of 1,024, 4,096, and 8,192 tokens. V2 cannot enable these fixed-budget modes. |
| GPT-5.1+ | `disabled` becomes OpenAI reasoning effort `none`; the other levels map directly. |
| Earlier GPT-5, o3, and o4 | Supports `low`, `medium`, and `high`, but not `disabled`. |
| GLM | Exposes only a switch: `disabled` turns thinking off and `high` turns it on. V2 does not support the switch. |
## BYOK behavior
* **V2:** `thinkingLevel` uses the existing model route. It does not add a
per-request BYOK switch.
* **V3:** set `useOwnKey: true` for models that require a provider key. Native
and BYOK routes use the same support matrix.
* **V4:** add an Anthropic, OpenAI, or Google key under **Settings → API Keys →
Bring Your Own Key**. V4 automatically uses a matching key; there is no
request flag. `modelParams` works the same with Browser Use-managed and
customer-managed keys.
## V2 and V3 examples
REST JSON uses the camelCase wire field `thinkingLevel` in both API versions.
```bash API V3 theme={null}
curl -X POST https://api.browser-use.com/api/v3/sessions \
-H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"task": "Compare three project-management tools",
"model": "claude-opus-4.7",
"thinkingLevel": "high"
}'
```
```bash API V2 theme={null}
curl -X POST https://api.browser-use.com/api/v2/tasks \
-H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"task": "Compare three project-management tools",
"llm": "browser-use-2.0",
"thinkingLevel": "high"
}'
```
## Validation errors
Unsupported V2/V3 model-level pairs return HTTP 422. The request field is
camelCase, while the validation detail currently names the backend field in
snake\_case:
```text theme={null}
Model "gemini-3.1-pro" (provider "google") does not support thinking_level="medium". Supported values: low, high
```
V4 validates the provider-native path and uses its wire name in the error:
```text theme={null}
Model "gemini-3.1-pro" does not support modelParams.thinkingConfig.thinkingLevel='minimal'. Supported values: high, low, medium
```
## Compatibility and API references
The normalized V2/V3 field is optional and nullable. New sessions use provider
defaults until a level is supplied. Existing V3 sessions retain their current
setting when the field is omitted; send `null` to clear it. The current Python
and TypeScript SDKs expose the V2/V3 and V4 reasoning fields in their request
types and client methods.
Generated V4 `modelParams` schema.
Generated V3 `thinkingLevel` schema.
Generated V2 `thinkingLevel` schema.
# Workspaces & files
Source: https://docs.browser-use.com/cloud/agent/workspaces
Persist files across V4 runs and conversations.
A **workspace** stores files that can be restored into later V4 runs, including
runs in a different session. Use it for inputs, scripts, and generated files.
A session holds the conversation; a workspace holds files; a profile holds
browser login state. Their IDs are different. Create and reuse workspaces
through API V4. A V3 workspace ID is not a V4 workspace ID.
Workspace files are synchronized to storage between runs. This is not a live
shared filesystem between simultaneous sessions: finish one writer before
starting another run that needs its changes.
## Upload and attach a file
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
workspace = client.workspaces.create(name="research")
client.workspaces.upload(workspace.id, "people.csv")
run = client.runs.create(
"Find everyone in the CSV who works at Google",
workspace_id=workspace.id,
)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const workspace = await client.workspaces.create({
name: "research",
});
await client.workspaces.upload(
workspace.id,
"people.csv",
);
const run = await client.runs.create({
task: "Find everyone in the CSV who works at Google",
model: "grok-4.5",
workspaceId: workspace.id,
});
```
V4 restores files uploaded to that workspace under `uploads/` when a run uses
its `workspaceId`. You do not need to repeat their IDs in `attachedFileIds`.
Staged attachments are different: attach their IDs when creating the run.
Files attached to a session remain available to its follow-ups. An explicitly
attached staged file takes precedence over a workspace upload with the same
stored filename. For a new conversation that needs persistent files, pass the
same `workspaceId`; reusing a workspace does not copy conversation history.
## Retrieve created files
Ask the agent to save its output, wait for the run to finish, then list the workspace:
```python Python theme={null}
result = client.runs.wait_for_completion(run.id)
files = client.workspaces.files(
workspace.id,
include_urls=True,
)
for file in files.files:
print(file.path, file.url)
```
```typescript TypeScript theme={null}
const result = await client.runs.waitForCompletion(run.id);
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. See the [workspace API
reference](/cloud/api-v4/workspaces/list-workspace-files) for pagination and
limits.
## Storage and upload limits
Check `GET /api/v4/workspaces/{workspace_id}/size` for `usedBytes` and
`maxBytes`. The current V4 upload allowance is 950 MiB per workspace for every
account, with 50 MiB per file. It leaves room for runtime files on the agent's
1 GiB persistent disk. An upload quota is separate from the number of
workspaces you can create, and from temporary disk used by a running agent.
A 413 response means a file or workspace limit was exceeded. Remove unneeded
files, split large files, or use a fresh workspace. Do not retry an unchanged
oversized upload. Deleting a session, archiving a workspace, and deleting a
workspace are different operations; archiving is not a file-deletion request.
Download a file promptly or request a fresh URL when its link expires. A
short-lived download URL does not mean the stored file expires after 60 seconds.
# API Reference
Source: https://docs.browser-use.com/cloud/api-reference
Authenticate and start using the Browser Use REST API.
## 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
```
See [Thinking levels](/cloud/agent/thinking-levels) for V3 model support,
follow-up behavior, BYOK handling, and a `thinkingLevel` request example.
## Quick example
```bash Create a session theme={null}
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) theme={null}
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 theme={null}
export BROWSER_USE_API_KEY=bu_your_key_here
```
***
Prefer the SDK? See the [Agent docs](/cloud/agent/quickstart) — the SDK has all API endpoints available as methods, including `client.browsers.create()`.
```bash Python theme={null}
pip install browser-use-sdk
```
```bash TypeScript theme={null}
npm install browser-use-sdk
```
# API key
Source: https://docs.browser-use.com/cloud/api-v2-overview
Set your API key to access the Browser Use v2 REST API.
Get a key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys\&new=1), then:
```bash theme={null}
export BROWSER_USE_API_KEY=your_key
```
Base URL: `https://api.browser-use.com/api/v2`
See [Thinking levels](/cloud/agent/thinking-levels) for V2 model support,
provider mappings, and a `thinkingLevel` request example.
***
Prefer the SDK? See the [Agent (v2) docs](/cloud/legacy/agent).
```bash Python theme={null}
pip install browser-use-sdk
```
```bash TypeScript theme={null}
npm install browser-use-sdk
```
# Get Account Billing
Source: https://docs.browser-use.com/cloud/api-v2/billing/get-account-billing
/cloud/openapi/v2.json get /billing/account
Get authenticated account information including credit balance and account details.
# Create Browser Session
Source: https://docs.browser-use.com/cloud/api-v2/browsers/create-browser-session
/cloud/openapi/v2.json post /browsers
Create a new browser session.
**Pricing:** Browser sessions are charged at $0.02/hour for all users.
The full rate is charged upfront when the session starts.
When you stop the session, any unused time is automatically refunded proportionally.
Billing is rounded up to the minute (minimum 1 minute).
For example, if you stop a session after 30 minutes, you'll be refunded half the charged amount.
**Session Limits:**
- All users: Up to 4 hours per session
# Get Browser Session
Source: https://docs.browser-use.com/cloud/api-v2/browsers/get-browser-session
/cloud/openapi/v2.json get /browsers/{session_id}
Get detailed browser session information including status and URLs.
# List Browser Session Downloads
Source: https://docs.browser-use.com/cloud/api-v2/browsers/list-browser-session-downloads
/cloud/openapi/v2.json get /browsers/{session_id}/downloads
List files the browser downloaded to S3 during the session.
Pass ``includeUrls=true`` to receive presigned download URLs (15 min expiry) inline.
Files are stored at ``downloads/projects/{project_id}/sessions/{session_id}/`` in
the private bucket.
# List Browser Sessions
Source: https://docs.browser-use.com/cloud/api-v2/browsers/list-browser-sessions
/cloud/openapi/v2.json get /browsers
Get paginated list of browser sessions with optional status filtering.
List responses intentionally omit per-session presigned recording URLs
(`recording_url` is always `null` here). Each recording URL requires a
synchronous boto3 SigV4 signing call (plus an S3 HEAD) that holds the GIL
and blocks the event loop. With page_size up to the max this CPU-pegs the
worker and starves the DB pool, cascading into pool exhaustion across the
fleet. Clients should fetch the recording URL on demand via
`GET /api/v2/browsers/{id}`, which signs exactly one URL for a single
session. Mirrors the v3 sessions list fix (ENG-4904, PR #4621).
# Update Browser Session
Source: https://docs.browser-use.com/cloud/api-v2/browsers/update-browser-session
/cloud/openapi/v2.json patch /browsers/{session_id}
Stop a browser session.
**Refund:** When you stop a session, unused time is automatically refunded.
If the session ran for less than 1 hour, you'll receive a proportional refund.
Billing is ceil to the nearest minute (minimum 1 minute).
# Agent Session Upload File Presigned Url
Source: https://docs.browser-use.com/cloud/api-v2/files/agent-session-upload-file-presigned-url
/cloud/openapi/v2.json post /files/sessions/{session_id}/presigned-url
Generate a secure presigned URL for uploading files to an agent session.
# Browser Session Upload File Presigned Url
Source: https://docs.browser-use.com/cloud/api-v2/files/browser-session-upload-file-presigned-url
/cloud/openapi/v2.json post /files/browsers/{session_id}/presigned-url
Generate a secure presigned URL for uploading files to a browser session.
# Get Task Output File Presigned Url
Source: https://docs.browser-use.com/cloud/api-v2/files/get-task-output-file-presigned-url
/cloud/openapi/v2.json get /files/tasks/{task_id}/output-files/{file_id}
Get secure download URL for an output file generated by the AI agent.
# Create Profile
Source: https://docs.browser-use.com/cloud/api-v2/profiles/create-profile
/cloud/openapi/v2.json post /profiles
Profiles allow you to preserve the state of the browser between tasks.
They are most commonly used to allow users to preserve the log-in state in the agent between tasks.
You'd normally create one profile per user and then use it for all their tasks.
You can set a `user_id` when creating a profile to associate it with a user in your system.
This allows you to later search for the profile using the GET /profiles endpoint with a query parameter.
# Delete Browser Profile
Source: https://docs.browser-use.com/cloud/api-v2/profiles/delete-browser-profile
/cloud/openapi/v2.json delete /profiles/{profile_id}
Permanently delete a browser profile and its configuration.
# Get Profile
Source: https://docs.browser-use.com/cloud/api-v2/profiles/get-profile
/cloud/openapi/v2.json get /profiles/{profile_id}
Get profile details.
# List Profiles
Source: https://docs.browser-use.com/cloud/api-v2/profiles/list-profiles
/cloud/openapi/v2.json get /profiles
Get paginated list of profiles.
Use the `query` parameter to search profiles by name or user_id.
This is useful when you have many profiles and need to find a specific user.
Example: If you set `user_id` to your internal user identifier when creating profiles,
you can later search for that user by passing their identifier as the `query` parameter.
# Update Profile
Source: https://docs.browser-use.com/cloud/api-v2/profiles/update-profile
/cloud/openapi/v2.json patch /profiles/{profile_id}
Update a browser profile's information.
# Create Session
Source: https://docs.browser-use.com/cloud/api-v2/sessions/create-session
/cloud/openapi/v2.json post /sessions
Create a new session with a new task.
# Create Session Public Share
Source: https://docs.browser-use.com/cloud/api-v2/sessions/create-session-public-share
/cloud/openapi/v2.json post /sessions/{session_id}/public-share
Create or return existing public share for a session.
# Delete Session
Source: https://docs.browser-use.com/cloud/api-v2/sessions/delete-session
/cloud/openapi/v2.json delete /sessions/{session_id}
Delete a session with all its tasks.
# Delete Session Public Share
Source: https://docs.browser-use.com/cloud/api-v2/sessions/delete-session-public-share
/cloud/openapi/v2.json delete /sessions/{session_id}/public-share
Remove public share for a session.
# Get Session
Source: https://docs.browser-use.com/cloud/api-v2/sessions/get-session
/cloud/openapi/v2.json get /sessions/{session_id}
Get detailed session information including status, URLs, and task details.
# Get Session Public Share
Source: https://docs.browser-use.com/cloud/api-v2/sessions/get-session-public-share
/cloud/openapi/v2.json get /sessions/{session_id}/public-share
Get public share information including URL and usage statistics.
# List Sessions
Source: https://docs.browser-use.com/cloud/api-v2/sessions/list-sessions
/cloud/openapi/v2.json get /sessions
Get paginated list of AI agent sessions with optional status filtering.
# Purge Session
Source: https://docs.browser-use.com/cloud/api-v2/sessions/purge-session
/cloud/openapi/v2.json post /sessions/{session_id}/purge
Immediately purge all data for a session (ZDR projects only).
Redacts PII from database records and deletes all S3 objects (screenshots,
output files, uploaded files, logs, history, downloads, agent state) for the
given session. This is the same cleanup the ZDR cron performs, but on-demand
and scoped to a single session with no grace period.
# Update Session
Source: https://docs.browser-use.com/cloud/api-v2/sessions/update-session
/cloud/openapi/v2.json patch /sessions/{session_id}
Stop a session and all its running tasks.
# Clone Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills-marketplace/clone-skill
/cloud/openapi/v2.json post /marketplace/skills/{skill_id}/clone
Clone a public marketplace skill to the user's project.
# Execute Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills-marketplace/execute-skill
/cloud/openapi/v2.json post /marketplace/skills/{skill_id}/execute
Execute a skill with the provided parameters.
# Get Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills-marketplace/get-skill
/cloud/openapi/v2.json get /marketplace/skills/{skill_slug}
Get details of a specific public skill from the marketplace.
# List Skills
Source: https://docs.browser-use.com/cloud/api-v2/skills-marketplace/list-skills
/cloud/openapi/v2.json get /marketplace/skills
List all public skills available in the marketplace with optional filtering.
# Cancel Generation
Source: https://docs.browser-use.com/cloud/api-v2/skills/cancel-generation
/cloud/openapi/v2.json post /skills/{skill_id}/cancel
Cancel the current in-progress generation for a skill.
# Create Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/create-skill
/cloud/openapi/v2.json post /skills
Create a new skill via automated generation.
# Delete Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/delete-skill
/cloud/openapi/v2.json delete /skills/{skill_id}
Delete a skill owned by the project.
# Execute Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/execute-skill
/cloud/openapi/v2.json post /skills/{skill_id}/execute
Execute a skill with the provided parameters.
# Get Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/get-skill
/cloud/openapi/v2.json get /skills/{skill_id}
Get details of a specific skill owned by the project.
# Get Skill Execution Output
Source: https://docs.browser-use.com/cloud/api-v2/skills/get-skill-execution-output
/cloud/openapi/v2.json get /skills/{skill_id}/executions/{execution_id}/output
Get presigned URL for downloading skill execution output.
# List Skill Executions
Source: https://docs.browser-use.com/cloud/api-v2/skills/list-skill-executions
/cloud/openapi/v2.json get /skills/{skill_id}/executions
List executions for a specific skill.
# List Skills
Source: https://docs.browser-use.com/cloud/api-v2/skills/list-skills
/cloud/openapi/v2.json get /skills
List all skills owned by the authenticated project with optional filtering.
# Refine Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/refine-skill
/cloud/openapi/v2.json post /skills/{skill_id}/refine
Refine a skill based on feedback.
# Rollback Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/rollback-skill
/cloud/openapi/v2.json post /skills/{skill_id}/rollback
Rollback to the previous version (cannot be undone).
# Update Skill
Source: https://docs.browser-use.com/cloud/api-v2/skills/update-skill
/cloud/openapi/v2.json patch /skills/{skill_id}
Update skill metadata (name, description, enabled, etc.).
# Create Task
Source: https://docs.browser-use.com/cloud/api-v2/tasks/create-task
/cloud/openapi/v2.json post /tasks
Create and start a new task.
You can either:
1. Start a new task without a sessionId (auto-creates a session with US proxy by default).
Note: Tasks without a sessionId are one-off tasks that automatically close the session
upon completion (keep_alive=false). Use sessionSettings to configure the auto-created
session (e.g. proxyCountryCode, profileId, screen dimensions).
2. Start a new task in an existing session (reuse for follow-up tasks or custom configuration)
Note: Without sessionSettings, a US proxy is enabled by default. Providing sessionSettings
overrides defaults — proxy is only enabled if proxyCountryCode is set. For full control over
session configuration (e.g. keep_alive), create a session first via POST /sessions with your
desired settings, then pass that sessionId when creating tasks.
# Get Task
Source: https://docs.browser-use.com/cloud/api-v2/tasks/get-task
/cloud/openapi/v2.json get /tasks/{task_id}
Get detailed task information including status, progress, steps, and file outputs.
# Get Task Logs
Source: https://docs.browser-use.com/cloud/api-v2/tasks/get-task-logs
/cloud/openapi/v2.json get /tasks/{task_id}/logs
Get secure download URL for task execution logs with step-by-step details.
# Get Task Status
Source: https://docs.browser-use.com/cloud/api-v2/tasks/get-task-status
/cloud/openapi/v2.json get /tasks/{task_id}/status
Lightweight endpoint optimized for polling task status.
Returns only the task status, output, and cost without loading steps,
files, or session details. Use this endpoint for efficient polling
instead of GET /tasks/{task_id}.
Recommended polling pattern:
1. POST /tasks to create a task
2. Poll GET /tasks/{task_id}/status until status is 'finished' or 'stopped'
3. GET /tasks/{task_id} once at the end for full details including steps
# List Tasks
Source: https://docs.browser-use.com/cloud/api-v2/tasks/list-tasks
/cloud/openapi/v2.json get /tasks
Get paginated list of AI agent tasks with optional filtering by session and status.
# Update Task
Source: https://docs.browser-use.com/cloud/api-v2/tasks/update-task
/cloud/openapi/v2.json patch /tasks/{task_id}
Control task execution with stop, pause, resume, or stop task and session actions.
# Get Account Billing
Source: https://docs.browser-use.com/cloud/api-v3/billing/get-account-billing
/cloud/openapi/v3.json get /billing/account
Get authenticated account information including credit balance and account details.
# Create Browser Session
Source: https://docs.browser-use.com/cloud/api-v3/browsers/create-browser-session
/cloud/openapi/v3.json post /browsers
Create a new browser session.
**Pricing:** Browser sessions are charged at $0.02/hour for all users.
The full rate is charged upfront when the session starts.
When you stop the session, any unused time is automatically refunded proportionally.
Billing is rounded up to the minute (minimum 1 minute).
For example, if you stop a session after 30 minutes, you'll be refunded half the charged amount.
**Session Limits:**
- All users: Up to 4 hours per session
# Get Browser Session
Source: https://docs.browser-use.com/cloud/api-v3/browsers/get-browser-session
/cloud/openapi/v3.json get /browsers/{session_id}
Get detailed browser session information including status and URLs.
# List Browser Session Downloads
Source: https://docs.browser-use.com/cloud/api-v3/browsers/list-browser-session-downloads
/cloud/openapi/v3.json get /browsers/{session_id}/downloads
List files the browser downloaded to S3 during the session.
Pass ``includeUrls=true`` to receive presigned download URLs (15 min expiry) inline.
Files are stored at ``downloads/projects/{project_id}/sessions/{session_id}/`` in
the private bucket.
# List Browser Sessions
Source: https://docs.browser-use.com/cloud/api-v3/browsers/list-browser-sessions
/cloud/openapi/v3.json get /browsers
Get paginated list of browser sessions with optional status filtering.
List responses intentionally omit per-session presigned recording URLs
(`recording_url` is always `null` here). Each recording URL requires a
synchronous boto3 SigV4 signing call (plus an S3 HEAD) that holds the GIL
and blocks the event loop. With page_size up to the max this CPU-pegs the
worker and starves the DB pool, cascading into pool exhaustion across the
fleet. Clients should fetch the recording URL on demand via
`GET /api/v2/browsers/{id}`, which signs exactly one URL for a single
session. Mirrors the v3 sessions list fix (ENG-4904, PR #4621).
# Update Browser Session
Source: https://docs.browser-use.com/cloud/api-v3/browsers/update-browser-session
/cloud/openapi/v3.json patch /browsers/{session_id}
Stop a browser session.
**Refund:** When you stop a session, unused time is automatically refunded.
If the session ran for less than 1 hour, you'll receive a proportional refund.
Billing is ceil to the nearest minute (minimum 1 minute).
# Create Profile
Source: https://docs.browser-use.com/cloud/api-v3/profiles/create-profile
/cloud/openapi/v3.json post /profiles
Profiles allow you to preserve the state of the browser between tasks.
They are most commonly used to allow users to preserve the log-in state in the agent between tasks.
You'd normally create one profile per user and then use it for all their tasks.
You can set a `user_id` when creating a profile to associate it with a user in your system.
This allows you to later search for the profile using the GET /profiles endpoint with a query parameter.
# Delete Browser Profile
Source: https://docs.browser-use.com/cloud/api-v3/profiles/delete-browser-profile
/cloud/openapi/v3.json delete /profiles/{profile_id}
Permanently delete a browser profile and its configuration.
# Get Profile
Source: https://docs.browser-use.com/cloud/api-v3/profiles/get-profile
/cloud/openapi/v3.json get /profiles/{profile_id}
Get profile details.
# List Profiles
Source: https://docs.browser-use.com/cloud/api-v3/profiles/list-profiles
/cloud/openapi/v3.json get /profiles
Get paginated list of profiles.
Use the `query` parameter to search profiles by name or user_id.
This is useful when you have many profiles and need to find a specific user.
Example: If you set `user_id` to your internal user identifier when creating profiles,
you can later search for that user by passing their identifier as the `query` parameter.
# Update Profile
Source: https://docs.browser-use.com/cloud/api-v3/profiles/update-profile
/cloud/openapi/v3.json patch /profiles/{profile_id}
Update a browser profile's information.
# Create Session
Source: https://docs.browser-use.com/cloud/api-v3/sessions/create-session
/cloud/openapi/v3.json post /sessions
Create a session and/or dispatch a task.
- Without session_id, without task: creates a new idle session (e.g. for file uploads).
- Without session_id, with task: creates a new session and dispatches the task.
- With session_id, with task: dispatches the task to an existing idle session.
- With session_id, without task: 422 — task is required when targeting an existing session.
If keep_alive is false (default), the session auto-stops when the task finishes.
If keep_alive is true, the session stays idle after the task, ready for follow-ups.
# Delete Session
Source: https://docs.browser-use.com/cloud/api-v3/sessions/delete-session
/cloud/openapi/v3.json delete /sessions/{session_id}
Soft-delete a session. Stops the sandbox first if still running.
# Get Session
Source: https://docs.browser-use.com/cloud/api-v3/sessions/get-session
/cloud/openapi/v3.json get /sessions/{session_id}
Get session details. Use this to poll for task completion and output.
# List Session Messages
Source: https://docs.browser-use.com/cloud/api-v3/sessions/list-session-messages
/cloud/openapi/v3.json get /sessions/{session_id}/messages
Return messages for a session with cursor-based pagination (chronological order).
# List Sessions
Source: https://docs.browser-use.com/cloud/api-v3/sessions/list-sessions
/cloud/openapi/v3.json get /sessions
List sessions for the authenticated project.
List responses intentionally omit per-session presigned URLs
(`screenshot_url`, `recording_urls`). Each presign is a synchronous boto3
SigV4 signing call that holds the GIL and blocks the event loop. With
page_size up to 100 this CPU-pegs the worker and starves the DB pool,
cascading into pool exhaustion across the fleet. Clients should fetch
these URLs on demand via `GET /api/v3/sessions/{id}`, which signs exactly
one screenshot URL plus any recording URLs for a single session.
# Stop Session
Source: https://docs.browser-use.com/cloud/api-v3/sessions/stop-session
/cloud/openapi/v3.json post /sessions/{session_id}/stop
Stop a session or the running task.
- strategy=session (default): destroy sandbox entirely, session → stopped.
- strategy=task: stop the running query, session stays alive (→ idle).
# Create Workspace
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/create-workspace
/cloud/openapi/v3.json post /workspaces
Create a new workspace for persistent shared file storage across sessions.
# Delete Workspace
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/delete-workspace
/cloud/openapi/v3.json delete /workspaces/{workspace_id}
Delete a workspace and its S3 data.
# Delete Workspace File
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/delete-workspace-file
/cloud/openapi/v3.json delete /workspaces/{workspace_id}/files
Delete a single file from a workspace.
# Get Workspace
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/get-workspace
/cloud/openapi/v3.json get /workspaces/{workspace_id}
Get workspace details.
# Get Workspace Size
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/get-workspace-size
/cloud/openapi/v3.json get /workspaces/{workspace_id}/size
Get current storage usage for a workspace.
# List Workspace Files
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/list-workspace-files
/cloud/openapi/v3.json get /workspaces/{workspace_id}/files
List files in a workspace's S3 prefix.
# List Workspaces
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/list-workspaces
/cloud/openapi/v3.json get /workspaces
Get paginated list of workspaces.
# Update Workspace
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/update-workspace
/cloud/openapi/v3.json patch /workspaces/{workspace_id}
Update a workspace's name.
# Upload Workspace Files
Source: https://docs.browser-use.com/cloud/api-v3/workspaces/upload-workspace-files
/cloud/openapi/v3.json post /workspaces/{workspace_id}/files/upload
Get presigned PUT URLs for uploading files to a workspace.
# X402 Balance
Source: https://docs.browser-use.com/cloud/api-v3/x402/x402-balance
/cloud/openapi/v3.json post /x402/balance
Read a wallet-derived project's credit balance auth by off-chain wallet signature
# API Reference
Source: 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.
## 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
```
See [Thinking levels](/cloud/agent/thinking-levels) for V4 `modelParams`,
provider-native reasoning values, and BYOK behavior.
## 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 theme={null}
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) theme={null}
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 theme={null}
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) theme={null}
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}'
```
## Direct browser control
Use the browser endpoints when your code will control Chrome over CDP instead of asking a hosted agent to do the work:
1. `POST /browsers` creates a browser and returns its `id` and `cdpUrl`.
2. Use `cdpUrl` with a local Browser Use agent (`Browser(cdp_url=...)`), Browser Use CLI (`BU_CDP_URL`), Playwright, Puppeteer, or Selenium.
3. `PATCH /browsers/{id}` with `{"action":"stop"}` stops the browser.
See [remote Browser Use](/open-source/customize/browser/remote), [Browser Use CLI](/open-source/browser-use-cli), or [Playwright, Puppeteer, Selenium](/cloud/browser/playwright-puppeteer-selenium) for complete connection examples.
## SDKs
The [Cloud SDK quick start](/cloud/agent/quickstart) wraps this loop — `runs.create()` then `runs.waitForCompletion()` / `runs.wait_for_completion()` — for TypeScript and Python.
# Create Browser Session
Source: https://docs.browser-use.com/cloud/api-v4/browsers/create-browser-session
/cloud/openapi/v4.json post /browsers
Create a new browser session.
**Pricing:** Browser sessions are charged at $0.02/hour for all users.
The full rate is charged upfront when the session starts.
When you stop the session, any unused time is automatically refunded proportionally.
Billing is rounded up to the minute (minimum 1 minute).
For example, if you stop a session after 30 minutes, you'll be refunded half the charged amount.
**Session Limits:**
- All users: Up to 4 hours per session
# Get Browser Session
Source: https://docs.browser-use.com/cloud/api-v4/browsers/get-browser-session
/cloud/openapi/v4.json get /browsers/{session_id}
Get detailed browser session information including status and URLs.
# List Browser Session Downloads
Source: https://docs.browser-use.com/cloud/api-v4/browsers/list-browser-session-downloads
/cloud/openapi/v4.json get /browsers/{session_id}/downloads
List files the browser downloaded to S3 during the session.
Pass ``includeUrls=true`` to receive presigned download URLs (15 min expiry) inline.
Files are stored at ``downloads/projects/{project_id}/sessions/{session_id}/`` in
the private bucket.
# List Browser Sessions
Source: https://docs.browser-use.com/cloud/api-v4/browsers/list-browser-sessions
/cloud/openapi/v4.json get /browsers
Get paginated list of browser sessions with optional status filtering.
List responses intentionally omit per-session presigned recording URLs
(`recording_url` is always `null` here). Each recording URL requires a
synchronous boto3 SigV4 signing call (plus an S3 HEAD) that holds the GIL
and blocks the event loop. With page_size up to the max this CPU-pegs the
worker and starves the DB pool, cascading into pool exhaustion across the
fleet. Clients should fetch the recording URL on demand via
`GET /api/v2/browsers/{id}`, which signs exactly one URL for a single
session. Mirrors the v3 sessions list fix (ENG-4904, PR #4621).
# Update Browser Session
Source: https://docs.browser-use.com/cloud/api-v4/browsers/update-browser-session
/cloud/openapi/v4.json patch /browsers/{session_id}
Stop a browser session.
**Refund:** When you stop a session, unused time is automatically refunded.
If the session ran for less than 1 hour, you'll receive a proportional refund.
Billing is ceil to the nearest minute (minimum 1 minute).
# Get Shared Session
Source: https://docs.browser-use.com/cloud/api-v4/get-shared-session
/cloud/openapi/v4.json get /share/{share_token}
Public transcript of a shared session (sensitive keys scrubbed).
Events come back in bounded pages: walk them by passing the previous
response's `nextAfter` back as `since` until `hasMore` is false (the wire
is camelCase — the Python fields are `next_after`/`has_more`). Run metadata
repeats on every page, so only `events` continues across pages.
Paging exists because this is an unauthenticated read and the whole
transcript in one response could occupy the event loop for seconds,
starving the shared Redis client's auth and rate-limit calls of their
50 ms deadline and silently failing them open.
Counts a view per full fetch. `since` makes this a live-tail poll: the
share page repeats the request every few seconds while a run is unfinished,
and each poll must stay cheap and must NOT inflate the view counter — a
viewer idling on a running session would otherwise register a view every
10 seconds (the counter's throttle window). Later pages of an initial load
carry a cursor too, so one load counts one view, not one per page.
# Get Shared Session Recording
Source: https://docs.browser-use.com/cloud/api-v4/get-shared-session-recording
/cloud/openapi/v4.json get /share/{share_token}/recording
Presigned URL for the shared session's browser recording (mp4).
Recording ONLY — the live-view URL is an interactive browser and is never
exposed through a share. Null until the session's browser stops and the
upload lands (same lag as GET /runs/{run_id}/recording). V4 keeps one
browser across follow-ups, so the newest run with a browser session is
the session's recording.
# Authorize Integration
Source: https://docs.browser-use.com/cloud/api-v4/integrations/authorize-integration
/cloud/openapi/v4.json post /integrations/{provider}/authorize
Get the OAuth redirect URL for connecting an integration.
The client opens the returned URL in a web auth session and polls
`/integrations/{provider}/status` once the flow lands back on the callback
path. The URL is Composio-hosted; no redirect URI is registered per client.
# Disconnect Integration
Source: https://docs.browser-use.com/cloud/api-v4/integrations/disconnect-integration
/cloud/openapi/v4.json delete /integrations/{provider}
Disconnect an integration for this project.
Deliberately NOT gated like `authorize` above: a restricted project must
still be able to tear down a connection it already has.
# Get Connection Status
Source: https://docs.browser-use.com/cloud/api-v4/integrations/get-connection-status
/cloud/openapi/v4.json get /integrations/{provider}/status
Check whether this project has connected a specific provider.
Poll this after opening the authorize URL to learn when OAuth completed.
# List Categories
Source: https://docs.browser-use.com/cloud/api-v4/integrations/list-categories
/cloud/openapi/v4.json get /integrations/categories
Get all integration categories, for filtering the list above.
# List Integrations
Source: https://docs.browser-use.com/cloud/api-v4/integrations/list-integrations
/cloud/openapi/v4.json get /integrations
List available integrations with this project's connection status.
`is_connected` is resolved per integration, so a client can render the whole
catalogue and the connected subset from this one call. `connected_only` is
what a "my integrations" view reads — including on a restricted project,
which cannot connect anything but can still remove what it already has.
# List Shared Session Files
Source: https://docs.browser-use.com/cloud/api-v4/list-shared-session-files
/cloud/openapi/v4.json get /share/{share_token}/files
Public listing of a shared session's workspace files. Same shape as the
authenticated workspace-files endpoint so the share page can reuse the
same components.
Scope is FORCED under `outputs/`: the worker contract promises the user
that only outputs/ files are deliverables, so working files the agent
left at the workspace root (notes, scripts, .env, uploads/) must never
become public through a share link.
# Create Profile
Source: https://docs.browser-use.com/cloud/api-v4/profiles/create-profile
/cloud/openapi/v4.json post /profiles
Profiles allow you to preserve the state of the browser between tasks.
They are most commonly used to allow users to preserve the log-in state in the agent between tasks.
You'd normally create one profile per user and then use it for all their tasks.
You can set a `user_id` when creating a profile to associate it with a user in your system.
This allows you to later search for the profile using the GET /profiles endpoint with a query parameter.
# Delete Browser Profile
Source: https://docs.browser-use.com/cloud/api-v4/profiles/delete-browser-profile
/cloud/openapi/v4.json delete /profiles/{profile_id}
Permanently delete a browser profile and its configuration.
# Get Profile
Source: https://docs.browser-use.com/cloud/api-v4/profiles/get-profile
/cloud/openapi/v4.json get /profiles/{profile_id}
Get profile details.
# List Profiles
Source: https://docs.browser-use.com/cloud/api-v4/profiles/list-profiles
/cloud/openapi/v4.json get /profiles
Get paginated list of profiles.
Use the `query` parameter to search profiles by name or user_id.
This is useful when you have many profiles and need to find a specific user.
Example: If you set `user_id` to your internal user identifier when creating profiles,
you can later search for that user by passing their identifier as the `query` parameter.
# Update Profile
Source: https://docs.browser-use.com/cloud/api-v4/profiles/update-profile
/cloud/openapi/v4.json patch /profiles/{profile_id}
Update a browser profile's information.
# Cancel Run
Source: https://docs.browser-use.com/cloud/api-v4/runs/cancel-run
/cloud/openapi/v4.json post /runs/{run_id}/cancel
Cancel an in-flight run.
Idempotent: a run already in a terminal state (completed / failed /
cancelled) is returned as-is. Once cancelled, the gateway refuses
every subsequent `chat_completions` call from the worker — so the
project cannot be billed further regardless of whether the worker
subprocess notices the cancel itself.
The worker keeps running for at most one more bcode step
(consuming no LLM tokens because CP 409s them) and then exits when
bcode realizes the next tool call has no model behind it.
# Create Run
Source: https://docs.browser-use.com/cloud/api-v4/runs/create-run
/cloud/openapi/v4.json post /runs
# Get Run
Source: https://docs.browser-use.com/cloud/api-v4/runs/get-run
/cloud/openapi/v4.json get /runs/{run_id}
# Get Run Events
Source: https://docs.browser-use.com/cloud/api-v4/runs/get-run-events
/cloud/openapi/v4.json get /runs/{run_id}/events
Paginated event read. `after` is the event id cursor — clients
doing periodic polls pass back the highest id they've seen to get only
the delta. Default 0 returns from the start.
# Get Run Status
Source: https://docs.browser-use.com/cloud/api-v4/runs/get-run-status
/cloud/openapi/v4.json get /runs/{run_id}/status
Minimal status poll (see RunStatusResponse). POLL rate bucket: selects
ONLY the status/project columns — a 2s poll never drags the task/result
text (TOAST reads) that GET /runs/{id} pays. Poll until terminal, then
fetch the full summary once.
# List Run Attachments
Source: https://docs.browser-use.com/cloud/api-v4/runs/list-run-attachments
/cloud/openapi/v4.json get /runs/{run_id}/attachments
The upload files attached to a run, for rendering attachment chips.
Ordered to match the run's attached_file_ids.
# List Runs
Source: https://docs.browser-use.com/cloud/api-v4/runs/list-runs
/cloud/openapi/v4.json get /runs
# Cancel Queued Message
Source: https://docs.browser-use.com/cloud/api-v4/sessions/cancel-queued-message
/cloud/openapi/v4.json delete /sessions/{session_id}/queue/{message_id}
Remove a still-pending queued message; 409 once the drain has claimed it.
# Create Session Share
Source: https://docs.browser-use.com/cloud/api-v4/sessions/create-session-share
/cloud/openapi/v4.json post /sessions/{session_id}/share
Enable sharing: reactivates the session's existing share (stable token,
so a link that was toggled off keeps working when re-enabled) or creates
one on first use.
# Delete Session
Source: https://docs.browser-use.com/cloud/api-v4/sessions/delete-session
/cloud/openapi/v4.json delete /sessions/{session_id}
Delete a session: it disappears from the session lists and can no longer
be opened or continued.
Soft delete (mirrors V2's `is_archived`): the runs, events and usage records
stay in place so billing and analytics keep reconciling, and ZDR purge is the
only thing that ever destroys V4 content. Idempotent — deleting twice is a
no-op 204.
An in-flight run is cancelled first, and pending queued messages are
cancelled too; otherwise the drain would dispatch a follow-up run whose
`session_archived_at` is NULL and resurrect the session. Cancel is forwarded
to CP before anything is written (and outside the queue lock, per the
enqueue path's ordering), so a CP failure (502) leaves the session visible
rather than hiding a run that is still burning credits.
# Get Session
Source: https://docs.browser-use.com/cloud/api-v4/sessions/get-session
/cloud/openapi/v4.json get /sessions/{session_id}
Session detail — the same slim shape as one GET /sessions row (its
latest run). Doubles as the busy/idle poll for a conversation (POLL rate
bucket): slim columns only, indexed public_session_id lookup, never the
heavy result/token fields.
# Get Session Feedback
Source: https://docs.browser-use.com/cloud/api-v4/sessions/get-session-feedback
/cloud/openapi/v4.json get /sessions/{session_id}/feedback
Every vote across the session in one query — the run view calls this
once per session on load. A session the caller doesn't own returns an
empty list (not 404), matching what an unshared session with no votes
returns. Reads the PRIMARY, not the replica: this hydrates the thumbs the
user may have set seconds ago, and replica lag would render a just-cast
vote as silently lost.
# Get Session Queue Message
Source: https://docs.browser-use.com/cloud/api-v4/sessions/get-session-queue-message
/cloud/openapi/v4.json get /sessions/{session_id}/queue/{message_id}
Read one queued message including terminal handoff states.
# Get Session Share
Source: https://docs.browser-use.com/cloud/api-v4/sessions/get-session-share
/cloud/openapi/v4.json get /sessions/{session_id}/share
The session's share link (active or not); null if never shared.
# List Session Queue
Source: https://docs.browser-use.com/cloud/api-v4/sessions/list-session-queue
/cloud/openapi/v4.json get /sessions/{session_id}/queue
List open messages, recently delivered queued messages, and steering cutoffs.
Includes live dispatch claims so clients can restore an accepted queued
message after a refresh without briefly losing its waiting state.
Consumed QUEUE rows are returned too, capped at the most recent
_DELIVERED_QUEUE_HISTORY, plus up to _AGENCY_DELIVERED_QUEUE_HISTORY compact
Agency choice and generation receipts so inbox state survives refresh. A message absorbed mid-run is announced by a
`queued_input_injected` event carrying only its input id, so the chat has no
other source for the text to render. The cap bounds this response because
clients poll it while a run is active; a session with more delivered
follow-ups than the cap renders its oldest injected bubbles without text.
The latest consumed interrupt bridges the primary-write/read-replica gap
before its replacement run appears; clients discard it once that run is
visible. Compact per-source cutoffs preserve every historical interrupted
transcript boundary without returning every historical steering message and
its attachments.
# List Sessions
Source: https://docs.browser-use.com/cloud/api-v4/sessions/list-sessions
/cloud/openapi/v4.json get /sessions
List the project's V4 sessions — one row per session (its most recent
run), most-recent first.
A session is a conversation: follow-up runs reuse the same
public_session_id, so GET /api/v4/runs would list one row per RUN. This
collapses to one row per SESSION directly in Postgres with DISTINCT ON, so a
chatty session with 100 runs is still a single row (no scan amplification),
and selects only the slim columns the session list needs — never the heavy
per-run text/token/cost fields.
Keyset-paginated on the latest run's (created_at, id): pass `next_cursor`
back as `cursor` to page. Offset/COUNT was dropped — it doesn't scale on
projects with many sessions.
# Purge Session
Source: https://docs.browser-use.com/cloud/api-v4/sessions/purge-session
/cloud/openapi/v4.json post /sessions/{session_id}/purge
Immediately purge all data for a V4 session (ZDR projects only).
Redacts DB records and deletes S3 objects (bcode state, workspace files,
recordings, downloads, staging uploads) for every run in the session. Same
cleanup the ZDR cron performs, but on-demand and with no grace period. V4
analog of the V2 POST /sessions/{id}/purge. Surfaces shared with a still-live
sibling run are deferred by the same guards the cron uses.
# Queue Session Message
Source: https://docs.browser-use.com/cloud/api-v4/sessions/queue-session-message
/cloud/openapi/v4.json post /sessions/{session_id}/queue
Send a message to a session. If the session is busy it waits on the queue
and runs as the next turn; if idle it drains immediately.
`interrupt=true` cancels the session's active run so this message takes effect
now instead of waiting for the current turn (no-op when the session is idle).
# Set Session Feedback
Source: https://docs.browser-use.com/cloud/api-v4/sessions/set-session-feedback
/cloud/openapi/v4.json post /sessions/{session_id}/feedback
Upsert one chat item's thumbs vote; `feedback_type: null` clears it.
# Update Session
Source: https://docs.browser-use.com/cloud/api-v4/sessions/update-session
/cloud/openapi/v4.json patch /sessions/{session_id}
Rename a session.
`session_title` is denormalized onto every run in the session (the session
list reads the latest run), so a rename writes them all in one UPDATE. Null
clears the name and the UI falls back to the opening task — it does NOT
re-run title generation, which only fills rows where the title IS NULL and
so would silently overwrite the clear.
# Update Session Share
Source: https://docs.browser-use.com/cloud/api-v4/sessions/update-session-share
/cloud/openapi/v4.json put /sessions/{session_id}/share
Toggle the session's share link on/off.
# Get Stripe Link Status
Source: https://docs.browser-use.com/cloud/api-v4/wallets/get-stripe-link-status
/cloud/openapi/v4.json get /stripe-link
The project's active Stripe Link wallet connection, if any.
Read-only: connecting happens in the cloud UI. A run opts into paying with
it by passing `connection_id` as RunCreateRequest.stripe_link_connection_id,
which is validated again there against this same project.
# List Agentcard Wallets
Source: https://docs.browser-use.com/cloud/api-v4/wallets/list-agentcard-wallets
/cloud/openapi/v4.json get /agentcard/wallets
The project's active AgentCard wallets, newest first.
Read-only: funding is admin-only during the beta. The UI uses this to offer
a wallet on the composer, which is how a run opts into spending — the id
then rides RunCreateRequest.agentcard_wallet_id and is validated again there
against this same project.
# Create Workspace
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/create-workspace
/cloud/openapi/v4.json post /workspaces
Mint an empty workspace. Lets callers upload input files BEFORE the
first run exists; pass the id as workspaceId on POST /runs.
# Delete Workspace
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/delete-workspace
/cloud/openapi/v4.json delete /workspaces/{workspace_id}
Archive a workspace. Idempotent for missing, archived, and foreign ids.
# Delete Workspace File
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/delete-workspace-file
/cloud/openapi/v4.json delete /workspaces/{workspace_id}/files
Delete one exact path from a workspace and remove matching upload metadata.
# Get Workspace
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/get-workspace
/cloud/openapi/v4.json get /workspaces/{workspace_id}
# Get Workspace Size
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/get-workspace-size
/cloud/openapi/v4.json get /workspaces/{workspace_id}/size
Return current storage usage and the plan-specific workspace quota.
# List Workspace Files
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/list-workspace-files
/cloud/openapi/v4.json get /workspaces/{workspace_id}/files
List files the agent has produced for this workspace, paginated.
# Update Workspace
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/update-workspace
/cloud/openapi/v4.json patch /workspaces/{workspace_id}
Rename a workspace. Explicit null clears the name; omission is a no-op.
# Upload Workspace Files
Source: https://docs.browser-use.com/cloud/api-v4/workspaces/upload-workspace-files
/cloud/openapi/v4.json post /workspaces/{workspace_id}/files/upload
Presigned PUT URLs for input files, placed under ``uploads/`` in the
workspace. The agent pulls them in at the start of every run. URLs are
pinned to the declared Content-Type and exact Content-Length.
# CAPTCHA handling
Source: https://docs.browser-use.com/cloud/browser/captcha-handling
Cloud browsers solve supported CAPTCHA challenges automatically.
Automatic CAPTCHA solving is enabled by default for API V4 Agent runs and
standalone Cloud Browser sessions.
For a standalone browser created with `POST /api/v4/browsers`, set
`"solveCaptchas": false` to handle challenges yourself. This standalone-browser
field is not currently part of V4 run `browserSettings`.
The automatic solver handles **CAPTCHAs**. Cloudflare and other bot walls are handled by the browser's stealth layer and residential proxies, not by the CAPTCHA solver.
Other anti-bot pages may still be passed by the browser's stealth protections
or residential proxy. That is separate from automatic CAPTCHA solver coverage.
## When a CAPTCHA appears
Stop driving the page, wait about 10 seconds, then inspect it again. Do not
refresh, click the challenge, or replace the browser while the solver is
working.
CAPTCHA solving is not guaranteed. If the challenge remains, open the
[live preview](/cloud/browser/live-preview) for human control. If the whole
site is blocking the browser rather than showing a challenge, try a different
[proxy country](/cloud/browser/proxies).
For a specific CAPTCHA or anti-bot issue, email [contact@browser-use.com](mailto:contact@browser-use.com).
# Live preview & recording
Source: https://docs.browser-use.com/cloud/browser/live-preview
Watch an API V4 run in real time or record its browser.
Wait for `browser.ready` as soon as you create the run so the URL is available
while the browser is live:
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
run = client.runs.create("Find the top Hacker News story")
ready = client.runs.wait_for_event(run.id, "browser.ready", interval=3)
print(ready.data["live_view_url"])
```
```typescript TypeScript theme={null}
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 ready = await client.runs.waitForEvent(run.id, "browser.ready", { interval: 3_000 });
console.log(ready.data.live_view_url);
```
These examples poll immediately, then every three seconds (Python uses seconds;
TypeScript uses milliseconds). Setting the interval explicitly also reduces
traffic on older SDK releases whose event default is one second. Event reads
share the project's general request budget; [stagger concurrent
waits](/cloud/guides/concurrency#when-you-need-events) and leave room for other calls.
The helper advances the event cursor and times out after five minutes. See [run
events](/cloud/agent/observability) for the full event stream and custom polling.
## Embed the live browser
```html theme={null}
```
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.
### Read-only embeds
To let users watch without clicking, typing, or scrolling the remote page,
make the iframe non-interactive in your application. This works with V4 today
and provides the same UI-level restriction as the V2 dashboard's view-only mode.
No API request parameter is needed.
Use `ready.data.live_view_url` from the V4 `browser.ready` event above.
For REST integrations, read that event from
[`GET /api/v4/runs/{run_id}/events`](/cloud/api-v4/runs/get-run-events).
Standalone [`POST /api/v4/browsers`](/cloud/api-v4/browsers/create-browser-session)
responses return the URL as `liveUrl`.
```html theme={null}
```
The preview keeps streaming. The `inert` wrapper prevents focus and keyboard
input, while `pointer-events: none` blocks pointer interaction with the iframe.
Use a browser that supports `inert`. To allow human takeover, remove `inert`,
`tabindex="-1"`, and `pointer-events: none` when your application enables control.
`ui=false` only hides the tabs and toolbar; it does not disable interaction.
View-only embeds are a UI restriction, not a server-enforced permission.
Anyone who opens the live URL directly or uses the underlying CDP URL can
still control the browser. Keep both URLs private.
### Read-only URL option (staging)
The staging viewer also supports `readOnly=true`. It blocks browser controls
and page input while keeping connection and screencast Retry buttons usable:
```javascript theme={null}
const previewUrl = new URL(liveViewUrl);
previewUrl.searchParams.set("readOnly", "true");
previewUrl.searchParams.set("ui", "false"); // Optional: hide tabs and toolbar.
iframe.src = previewUrl.toString();
```
This option is not yet available on `live.browser-use.com`; use the iframe
wrapper above in production. It is a viewer URL option, not a field in the
browser or run creation request. Omitting it or setting it to `false` keeps
the viewer interactive, and `/session/{id}` links preserve it when redirecting.
Like the wrapper, it prevents UI input without changing CDP access permissions.
## Recording
Enable recording when the run creates its browser:
```python Python theme={null}
run = client.runs.create(
"Test the checkout flow",
browser_settings={"record": True},
)
```
```typescript TypeScript theme={null}
const run = await client.runs.create({
task: "Test the checkout flow",
model: "grok-4.5",
browserSettings: { proxyCountryCode: "us", record: true },
});
```
```bash curl theme={null}
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}}'
```
The MP4 becomes available in the Dashboard after the browser stops. API runs
default to recording off, and Zero Data Retention projects never record.
For standalone browsers, use `enableRecording: true` in
`POST /api/v4/browsers` (`enable_recording=True` in the Python SDK).
For agent-created browsers, use `browserSettings.record: true` as above.
These are different request shapes.
After stopping a standalone browser, poll `GET /api/v4/browsers/{id}` for
`recordingUrl`. The stop response and browser-list response do not contain a
ready recording URL. Stop polling when `recordingAvailable` is false; the
video cannot appear for that session. If the installed SDK does not expose
that field, use the REST response and the current API reference.
# Playwright, Puppeteer, Selenium
Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium
Control a Browser Use cloud browser directly over CDP.
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).
## 1. Create a browser
```bash theme={null}
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 theme={null}
import os
from playwright.sync_api import sync_playwright
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 theme={null}
import { chromium } from "playwright";
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
```typescript theme={null}
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.BROWSER_USE_CDP_URL!,
});
const [page] = await browser.pages();
await page.goto("https://example.com");
console.log(await page.title());
```
### Selenium
Selenium's `debugger_address` only supports local `host:port` connections.
Use Playwright or Puppeteer for remote CDP over WebSocket.
## 3. Stop the browser
```bash theme={null}
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](/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.
## PDF rendering
Standalone browser creation accepts `"pdfRendererEnabled": false` to disable
Chrome's in-tab PDF viewer. PDFs are still saved to the browser session's
download directory. This does not block PDF downloads or reduce their bytes to
zero. The setting is not currently exposed in V4 agent `browserSettings`.
## Profiles and browser contexts
To reuse a saved login, supply `profileId` when creating the browser and use
the existing browser context shown above. Creating a fresh incognito context
with `new_context()` / `newContext()` does not inherit that profile's cookies.
A profile is not a guarantee that the site's login remains valid, and cookie
sync does not import saved passwords or promise a persistent HTTP disk cache.
See [Sync local and cloud cookies](/cloud/guides/profile-sync).
# Proxies
Source: https://docs.browser-use.com/cloud/browser/proxies
Configure residential or custom proxies for API V4 browsers and agent runs.
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.
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 theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
run = client.runs.create(
"Get the iPhone 16 price on amazon.de",
browser_settings={"proxyCountryCode": "de"},
)
```
```typescript TypeScript theme={null}
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" },
});
```
```bash curl theme={null}
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
Pass `null` for QA or internal sites that do not need a residential proxy:
```python Python theme={null}
run = client.runs.create(
"Test my staging site",
browser_settings={"proxyCountryCode": None},
)
```
```typescript TypeScript theme={null}
const run = await client.runs.create({
task: "Test my staging site",
model: "grok-4.5",
browserSettings: { proxyCountryCode: null },
});
```
## Custom proxy
Custom proxies are available to all accounts, including free and pay-as-you-go
accounts. No subscription is required. Normal credit, spend, and concurrency
limits still apply.
The request format depends on what you are creating:
| REST endpoint | Custom proxy field |
| ----------------------- | ----------------------------- |
| `POST /api/v4/browsers` | Top-level `customProxy` |
| `POST /api/v4/runs` | `browserSettings.customProxy` |
`browser_settings` is the Python SDK keyword for agent runs. In REST JSON,
the agent-run field is `browserSettings`.
### Standalone browsers
For a browser you control with Playwright, Puppeteer, or another CDP client,
put `customProxy` directly in the request body:
```bash curl theme={null}
curl https://api.browser-use.com/api/v4/browsers \
-H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customProxy": {
"host": "proxy.example.com",
"port": 8080,
"username": "YOUR_PROXY_USERNAME",
"password": "YOUR_PROXY_PASSWORD"
}
}'
```
Replace the proxy details with yours. Omit both `username` and `password` if
your proxy does not require authentication. Do not wrap these settings in
`browserSettings` or `browser_settings` on `/api/v4/browsers`.
Keep the returned browser ID and stop the browser with
`PATCH /api/v4/browsers/{id}` and `{"action":"stop"}` when finished. See the
[Create browser reference](/cloud/api-v4/browsers/create-browser-session) and
[Browser quickstart](/cloud/browser/quickstart).
### Agent runs
For a hosted agent, pass the proxy in the run's browser settings:
```python Python theme={null}
run = client.runs.create(
"Check the account dashboard",
browser_settings={
"customProxy": {
"host": "proxy.example.com",
"port": 8080,
"username": "user",
"password": "pass",
"ignoreCertErrors": False,
}
},
)
```
```typescript TypeScript theme={null}
const run = await client.runs.create({
task: "Check the account dashboard",
model: "grok-4.5",
browserSettings: {
proxyCountryCode: "us",
customProxy: {
host: "proxy.example.com",
port: 8080,
username: "user",
password: "pass",
ignoreCertErrors: false,
},
},
});
```
A custom proxy overrides `proxyCountryCode` and must be passed again when a
follow-up provisions a new browser. See the [Create run
reference](/cloud/api-v4/runs/create-run) for the complete settings object.
# Browser Infrastructure quickstart
Source: https://docs.browser-use.com/cloud/browser/quickstart
Launch a cloud browser and connect to it from your code.
Every browser includes stealth, proxies, live preview, and recording. Its
**CDP URL** is a WebSocket endpoint for remotely controlling Chrome.
Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys\&new=1) and
export it:
```bash theme={null}
export BROWSER_USE_API_KEY=your_key
```
## Install the SDK
Skip this step if you use curl.
```bash Python theme={null}
pip install --upgrade browser-use-sdk playwright
```
```bash Playwright theme={null}
npm install browser-use-sdk@latest playwright
```
```bash Puppeteer theme={null}
npm install browser-use-sdk@latest puppeteer-core
```
## Launch and connect
The SDK and REST examples use API V4. Each SDK example creates a browser,
connects over CDP, opens a page, and stops the managed browser in `finally`.
Use its existing browser context so profile cookies and managed settings apply.
No local browser download is needed for a remote CDP connection.
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
from playwright.sync_api import sync_playwright
with BrowserUse() as client:
session = client.browsers.create(proxy_country_code="us")
try:
if not session.cdp_url:
raise RuntimeError("The browser did not return a CDP URL")
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.cdp_url)
context = browser.contexts[0]
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://example.com")
print(page.title())
finally:
client.browsers.stop(session.id)
```
```typescript Playwright theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
import { chromium } from "playwright";
const client = new BrowserUse();
const session = await client.browsers.create({ proxyCountryCode: "us" });
try {
if (!session.cdpUrl) throw new Error("The browser did not return a CDP URL");
const browser = await chromium.connectOverCDP(session.cdpUrl);
try {
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();
await page.goto("https://example.com");
console.log(await page.title());
} finally {
await browser.close();
}
} finally {
await client.browsers.stop(session.id);
}
```
```typescript Puppeteer theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
import puppeteer from "puppeteer-core";
const client = new BrowserUse();
const session = await client.browsers.create({ proxyCountryCode: "us" });
try {
if (!session.cdpUrl) throw new Error("The browser did not return a CDP URL");
const browser = await puppeteer.connect({ browserWSEndpoint: session.cdpUrl });
try {
const context = browser.defaultBrowserContext();
const page = (await context.pages())[0] ?? await context.newPage();
await page.goto("https://example.com");
console.log(await page.title());
} finally {
await browser.disconnect();
}
} finally {
await client.browsers.stop(session.id);
}
```
```bash curl theme={null}
# Requires curl and jq. Run this in the shell used by your automation script.
browser=$(curl --fail-with-body -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 -er .id)
export BROWSER_USE_CDP_URL=$(echo "$browser" | jq -er .cdpUrl)
# Connect your Playwright or Puppeteer script to BROWSER_USE_CDP_URL.
# Keep this browser running until that script finishes, then stop it below.
```
Disconnecting CDP, Playwright's `browser.close()`, or `client.close()` does
not stop the managed browser. Use `client.browsers.stop(session.id)` as above.
With Puppeteer, use `disconnect()` to release the connection, then stop the
managed browser through the API.
For a browser created with curl, run this **after** your automation finishes:
```bash theme={null}
curl --fail-with-body -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"}'
```
Selenium cannot connect to a remote WebSocket CDP URL; use Playwright or
Puppeteer. Use the [live preview](/cloud/browser/live-preview) while the browser
is running, then stop it when you are done. Recording is off by default for API
browsers. Set `enable_recording=True` / `enableRecording: true` when creating the browser if you
need a recording. The video becomes available asynchronously after it stops;
Zero Data Retention projects never record.
Full connection guide and stop semantics.
Configure proxies, screen size, recording, and timeout.
# Stealth
Source: https://docs.browser-use.com/cloud/browser/stealth
Managed Chromium with stealth protections and proxy support.
See [how we perform in the hardest stealth benchmark](https://browser-use.com/posts/stealth-benchmark).
## What's included
Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed.
* **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Fingerprint test results vary with browser version and the target site.
* **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster.
* **Anti-bot protections** — Help with sites protected by Cloudflare, PerimeterX, and other detection services. Access to every site is not guaranteed.
Cloud browsers also solve CAPTCHAs automatically.
See [CAPTCHA handling](/cloud/browser/captcha-handling) for what to do when a
solver is still working or a site has a specific anti-bot issue.
## 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.
Stealth is included in the managed browser. It is not a separate subscription
upgrade you must buy to enable these protections. A target site can still
reject a browser or require verification; see [troubleshooting](/cloud/guides/troubleshooting).
# Choosing an Agent
Source: https://docs.browser-use.com/cloud/choosing-an-agent
Compare Browser Use Cloud agents on accuracy, speed and price, and pick the right one for your task.
Browser Use Cloud offers three agents that can complete your tasks. Use V4 when accuracy matters most, V3 when speed and cost matter most, and V2 only for older integrations.
## At a glance
| | V4 Agent | V3 Agent | V2 Agent |
| ---------------------- | ------------------------ | ------------------------------------- | ----------------------------------- |
| | Most accurate | Fastest | Legacy |
| Verdict | Choose for complex tasks | Choose for quick, cost-optimized work | Keep only for existing integrations |
| Accuracy on hard tasks | **76%** | 67% | 54% |
| Speed | slower, more thorough | **fastest on identical tasks** | quick on small tasks |
| Cost level | \$\$\$ | \$\$ | \$ |
### V4 Agent — most accurate
V4 writes and runs code to complete long, multi-step browser tasks. It can research across many pages, compare options, follow detailed instructions, collect records, and save the results as a spreadsheet or in any format.
**Best for:**
* Bulk data collection
* Complex tasks that span many pages and sites
* Long, difficult instructions
**Example tasks:**
* "Collect every document with its title, reference, and deadline."
* "Create a spreadsheet of all the pricing plans across these five competitors."
The most thorough option, but it takes its time.
### V3 Agent — fastest
V3 looks at the page and acts step-by-step like a human would. Use only when cost and speed matter more than accuracy.
**Best for:**
* Simple tasks, such as finding publicly available information
* Workloads where cost and speed are essential
* Open-ended research
**Example tasks:**
* "Get a shipping quote for a 3 lb package between two zip codes."
* "Check whether this product is in stock and what it costs right now."
Faster but less reliable.
### V2 Agent — legacy
Our first-generation agent remains available for compatibility. It is based on our open-source repository, but it is not actively maintained and is less accurate.
Starting something new? Use V4 instead; it's far more accurate.
## V4 completes the hardest tasks
V4 completed 76% of difficult, real-world browser tasks — nine points ahead of V3 and twenty-two ahead of V2.
## Typical tasks cost \$0.70–\$1.64
Measured with the same model — Claude Opus 4.7 — across the last 30 days of production usage, the typical customer's successful task costs \$0.70 on V2, \$0.87 on V3, and \$1.64 on V4, taking about 1, 2, and 5 minutes respectively. V4 costs more per task because customers use it for more complex work, and V2's tasks run shortest only because it gets the simplest ones. On identical hard tasks, V3 is the fastest agent (about 6 minutes, versus about 10 for V2 and V4).
| | V2 Agent | V3 Agent | V4 Agent |
| ------------- | -------- | -------- | -------- |
| Cost per task | \$0.70 | \$0.87 | \$1.64 |
| Time per task | \~1 min | \~2 min | \~5 min |
## Comparing accuracy, speed, and cost
## How we measured this
In July 2026, we ran each agent four times on Internal Bench Hard: 106 difficult tasks on real websites. Every run used Claude Opus 4.8, and the same independent judge scored each result. We left out tasks blocked by websites' anti-bot measures. The speed and cost figures are averages; simpler tasks usually run faster and cost less. Real-world cost and speed figures are the median customer's successful task with Claude Opus 4.7 over the last 30 days of production, our own testing excluded.
# FAQ
Source: https://docs.browser-use.com/cloud/faq
Common questions and solutions.
## Which model should I use?
* **GPT-5.6 Luna** (`gpt-5.6-luna`, recommended) — the best balance of accuracy, speed, and price for most tasks.
* **Claude Opus 5** (`claude-opus-5`) — maximum intelligence for difficult, long-horizon work.
* **Grok 4.5** (`grok-4.5`) — a strong general-purpose alternative.
* **GPT-5.6 Sol** (`gpt-5.6-sol`) — a higher-cost OpenAI option for complex tasks.
* **MiniMax M3** (`minimax-m3`) — inexpensive 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?
The V4 run's `browser.ready` event contains `live_view_url`. Embed it in an iframe or open it in a browser.
```python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
created = client.runs.create("Go to example.com")
client.runs.wait_for_completion(created.id)
events = client.runs.events(created.id, limit=100)
ready = next(event for event in events.events if event.type == "browser.ready")
print(ready.data["live_view_url"])
```
Poll events until `browser.ready` appears if you need the URL while the run is still active. See [Human in the loop](/cloud/agent/human-in-the-loop) for a complete flow.
## Getting blocked by a website
Stealth and proxies are active by default. If you're still getting blocked:
* **Use a profile** with logged-in cookies to bypass login walls.
* **Try a different proxy country** to match the target region.
If it still doesn't work, contact support inside the [Cloud Dashboard](https://cloud.browser-use.com) — send us a link to the page where you're getting blocked.
## Rate limited (429 errors)
HTTP request limits, browser concurrency, and a full session message queue can all return 429. Inspect the response before retrying: increasing browser concurrency does not increase the general request bucket used by V4 event reads.
Use bounded workers, stagger polling, and respect `Retry-After` when provided. The SDK retries 429 responses, but a sustained workload needs its own request budget. See [Concurrency and limits](/cloud/guides/concurrency) for account-limit lookup, polling budgets, queue behavior, and error handling.
For insufficient-balance or API-key spending-cap errors (402), see [Billing and credits](/cloud/guides/billing).
## V2 or V4 — which should I use?
Use **V4** for difficult tasks where accuracy matters. It supports:
* Run-focused API with a cheap status polling endpoint
* Conversation sessions with follow-ups
* Persistent workspaces and turn-scoped file attachments
* Incremental events for custom UIs and monitoring
* Per-run cost totals, cost caps, and optional judgement
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).
# 1Password & 2FA
Source: https://docs.browser-use.com/cloud/guides/1password
Auto-fill passwords and TOTP codes from 1Password in API tasks.
## Setup
### 1. Create a dedicated vault
Create a new vault in 1Password for Browser Use. Add the credentials you want the agent to access (usernames, passwords, and 2FA/TOTP codes).
### 2. Create a service account token
1. Go to [1Password Developer Tools - Service Accounts](https://my.1password.eu/developer-tools/active/service-accounts)
2. Click **New Service Account**, name it "Browser Use Cloud"
3. Grant **read access** to the dedicated vault
4. Copy the generated token
### 3. Connect to Browser Use Cloud
1. Go to [Browser Use Cloud Settings - Secrets](https://cloud.browser-use.com/settings?tab=secrets)
2. Click **Create Integration**
3. Paste your service account token
## Use a vault in a v4 run
Pass the vault id and the domains where its credentials may be typed. Browser Use finds the project's connected 1Password integration and makes the vault's username, password, and TOTP fields available to the run. You do not need the integration, item, or field ids.
Every supported credential field in the vault is made available to the run. Use a dedicated vault containing only the accounts the agent needs.
```bash theme={null}
curl -X POST 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": "Open amazon.com, log in, and check my recent orders",
"opVaultId": "",
"opVaultAllowedDomains": ["amazon.com"]
}'
```
* `opVaultAllowedDomains` is required with `opVaultId`. Use bare hostnames; a hostname also covers its subdomains.
* Vault items become aliases based on their titles, with `_username`, `_password`, or `_bu_2fa_code` suffixes.
* Vault fields and explicit `secretBindings` share a limit of 10 bindings per run. The API returns `422` if the combined total exceeds 10.
* Vault access requires exactly one active 1Password integration on the project and is unavailable for zero-data-retention projects.
These fields work through the v4 REST API. The currently published Python and TypeScript SDK types do not expose them yet; use the REST request above until those clients are regenerated.
## Bind individual fields in a v4 run
For least-privilege access, use **secret bindings** instead of exposing every credential field in a vault. Each binding names one field of one 1Password item, gives it an alias the agent can ask for, and lists the hosts where it may be typed. The server types the value into the focused field when the agent asks for the alias on an allowed domain. This keeps the raw value out of your task text, but does not make it inaccessible to the agent: once entered, it is available to the page and to browser/CDP access. Bindings are run-scoped, so a follow-up run that needs the same login must send them again.
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
with BrowserUse() as client:
run = client.runs.create(
"Log into Jira and create a ticket for the Q4 release",
secret_bindings=[
{
"alias": "jira_password",
"source": {
"type": "onepassword",
"integrationId": "",
"vaultId": "",
"itemId": "",
"fieldId": "password",
},
"allowedDomains": ["atlassian.net"],
}
],
)
print(client.runs.wait_for_completion(run.id).result)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const run = await client.runs.create({
task: "Log into Jira and create a ticket for the Q4 release",
secretBindings: [
{
alias: "jira_password",
source: {
type: "onepassword",
integrationId: "",
vaultId: "",
itemId: "",
fieldId: "password",
},
allowedDomains: ["atlassian.net"],
},
],
});
```
* `integrationId` is the 1Password integration you connected under **Settings → Secrets**.
* `vaultId` and `itemId` are the 1Password UUIDs of the vault and item (copy them from 1Password).
* `fieldId` is the field's id, not its label: `username`, `password`, or `one-time-password` for a TOTP field, which resolves to the current code.
* `allowedDomains` are bare hostnames; a host covers its subdomains.
In the chat UI at cloud.browser-use.com both options live under **Run settings → Credentials**. **Use a whole vault** asks for the vault and the allowed sites and sends `opVaultId` + `opVaultAllowedDomains`; **Add a credential** walks vault, item, and field, takes an alias and the domains, and sends one binding. The **Agents** page has a **1Password** section with the same vault + allowed-sites pair, and its REST snippet shows the resulting request.
## Use a vault in a v2 or v3 task
```python Python theme={null}
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"],
)
print(result.output)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk";
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"],
},
);
console.log(result.output);
```
For SSO/OAuth redirects, include all required domains:
```python Python theme={null}
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"],
)
```
```typescript TypeScript theme={null}
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"],
},
);
```
## How it works
When the agent encounters a login form:
1. It identifies the service (e.g., Twitter, GitHub, LinkedIn)
2. Retrieves matching credentials from your 1Password vault
3. Fills in the username and password
4. If 2FA is required and a TOTP code is stored, it generates and enters the code automatically
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.
# 2FA
Source: https://docs.browser-use.com/cloud/guides/2fa
Handle two-factor authentication in API V4 runs.
The most reliable options are a saved profile or a human checkpoint.
## Reuse a logged-in profile
[Sync your local login](/cloud/guides/profile-sync), then load that profile in
the run:
```python Python theme={null}
run = client.runs.create(
"Download my latest invoice",
browser_settings={"profileId": "YOUR_PROFILE_ID"},
)
```
```typescript TypeScript theme={null}
const run = await client.runs.create({
task: "Download my latest invoice",
model: "grok-4.5",
browserSettings: {
profileId: "YOUR_PROFILE_ID",
proxyCountryCode: "us",
},
});
```
This avoids another 2FA challenge while the site's cookies remain valid.
## Let a human take over
Ask the first run to stop at the 2FA screen, get its `live_view_url` from the
[`browser.ready` event](/cloud/agent/human-in-the-loop), and have the user enter
the code. Then continue with the same session:
```python Python theme={null}
first = client.runs.create(
"Open the login page and stop at the 2FA prompt",
)
client.runs.wait_for_completion(first.id)
next_run = client.runs.create(
"Continue after login and download the invoice",
session_id=first.session_id,
)
```
```typescript TypeScript theme={null}
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,
});
```
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.
# Profiles
Source: https://docs.browser-use.com/cloud/guides/authentication
Reuse cookies and browser state in API V4 runs.
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.
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 theme={null}
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 theme={null}
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",
model: "grok-4.5",
browserSettings: {
profileId: "YOUR_PROFILE_ID",
proxyCountryCode: "us",
},
});
const result = await client.runs.waitForCompletion(run.id);
console.log(result.result);
```
```bash curl theme={null}
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"}}'
```
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.
For the fastest setup, [sync an existing local login](/cloud/guides/profile-sync).
# Billing and credits
Source: https://docs.browser-use.com/cloud/guides/billing
Pay-as-you-go credits, project balances, BYOK charges, auto recharge, and API-key spending caps.
Browser Use Cloud uses pay-as-you-go billing. Add credits to the project that owns your API key, then pay for usage. A recurring Browser Use subscription is not required for custom proxies or supported bring-your-own-provider-key (BYOK) usage. Existing customers can retain legacy allowances and pricing.
Use the [pricing page](https://browser-use.com/pricing) for current model, browser, proxy, and network rates. New top-ups have a \$5 minimum and use whole-dollar amounts.
## Which credits does my API key use?
Credits belong to a **project**. All keys in that project use its balance. Funding one project does not fund a key from another project, even if both belong to you.
```bash theme={null}
curl --fail-with-body https://api.browser-use.com/api/v2/billing/account \
-H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY"
```
This endpoint works with the same key you use for V4. Compare `projectId` with the project selected in the dashboard. Read `totalCreditsBalanceUsd` for the reported total and `monthlyCreditsBalanceUsd` / `additionalCreditsBalanceUsd` for its components. A `null` `planInfo` is normal for a pay-as-you-go project; it does not mean the project cannot use paid features.
If a payment succeeded but the expected credits are missing, first check the project and the payment's status. When contacting support, include the project ID, payment time, amount, and receipt or invoice reference. Never include API keys or card details.
## Do credits expire or renew?
| Credit type | Behavior |
| ----------------------------------------- | ----------------------------------------------------- |
| Signup credits | A one-time grant, not a recurring monthly allowance. |
| Purchased top-up credits | Do not expire. |
| Included credits on a legacy monthly plan | Follow the plan's billing cycle and do not roll over. |
Concurrency tiers are based on qualifying payments to the project, not credits remaining in its wallet. Using purchased credits does not lower an already-granted concurrency allowance. Credit grants do not count as payments. Higher existing or legacy concurrency grants are preserved; see [Concurrency and limits](/cloud/guides/concurrency).
## What does BYOK pay for?
With a supported provider key, the model provider bills token usage to your provider account. Browser Use still charges an orchestration fee of **0.2× the corresponding model token cost**, plus browser and network usage. Keep both accounts funded.
Configure supported provider keys in **Settings → API Keys → Bring Your Own Key**. The dashboard requires adding credits before BYOK setup is available. A pay-as-you-go top-up satisfies that requirement; no recurring subscription is needed. Accounts that have never paid also have model eligibility restrictions.
A Claude consumer subscription is not an Anthropic API key. BYOK means a supported provider credential, not an arbitrary self-hosted model endpoint. See [Models](/cloud/agent/models) for model selection.
## Proxy and browser charges
A [custom proxy](/cloud/browser/proxies) replaces the Browser Use managed proxy. Your proxy provider bills its own usage, and Browser Use browser/network charges still apply. Disabling the managed proxy also leaves browser and direct network usage billable.
Run completion does not necessarily stop the browser. A browser may remain available for the next turn. When your application no longer needs it, explicitly stop the owned browser with `PATCH /api/v4/browsers/{id}` and `{"action":"stop"}`. Disconnecting Playwright/CDP or closing the SDK client is not the cloud stop operation. See [browser lifetime](/cloud/guides/concurrency#keep-track-of-browser-lifetime).
## Auto recharge
Auto recharge has two separate settings:
* **Threshold:** the balance below which another purchase is triggered.
* **Recharge amount:** the amount of credits purchased by that recharge.
For example, a \$10 threshold with a \$50 recharge amount purchases \$50 when the balance falls below \$10. It does not target a final balance of \$50.
Enabling auto recharge while already below the threshold can trigger a charge immediately. Choose an amount that covers your expected workload and check payment history if a recharge fails. Funding and concurrent workloads can change the displayed balance while a payment is being processed.
## API-key monthly spending caps
An API key can have a monthly USD spending cap. It is a **soft limit** on Browser Use charges recorded for that key during the current UTC calendar month, shared across that key's applicable usage.
When a cap is reached, new run/browser admission can return HTTP 402 with a structured detail:
```json theme={null}
{
"detail": {
"code": "api_key_monthly_spend_limit_reached",
"message": "API key monthly spend limit reached",
"cap": 50,
"spent": 50.25
}
}
```
Use `detail.code`, `cap`, and `spent` to distinguish this response from an insufficient project balance. Adding credits does not raise a key's cap. Check the cap in API key settings if the project still has credits.
Spend checks use a cached snapshot, and running or simultaneous work can finish above the cap. A cap is not a strict prepaid wallet or a guarantee that costs stop at exactly that amount. BYOK provider charges are billed separately by the provider. Combine caps with bounded concurrency, project balance monitoring, and per-run controls where available.
See [Concurrency and limits](/cloud/guides/concurrency#diagnose-errors-before-retrying) for 402, 409, and 429 handling.
# Concurrency and limits
Source: https://docs.browser-use.com/cloud/guides/concurrency
Preserved legacy allowances, spend tiers, browser lifetime, polling budgets, queues, and retry guidance.
Browser Use has separate limits for simultaneous browser work and API request frequency. A project can have available browser capacity and still receive a rate-limit response if it sends too many HTTP requests.
For reliable batch processing, use a bounded worker pool, track the browsers your application owns, and budget polling requests across the project.
## Check your project's capacity
Use your API key to inspect its project:
```bash theme={null}
curl --fail-with-body \
https://api.browser-use.com/api/v2/billing/account \
-H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY"
```
This billing endpoint is also useful when your application uses V4 runs. Read:
| Field | Meaning |
| ------------------------ | ------------------------------------------------------------------------------------------------- |
| `projectId` | The project this API key accesses. Check this if you funded a different project in the dashboard. |
| `apiKeyId` | The internal identifier of the authenticated key. |
| `concurrentSessionLimit` | The project's current browser concurrency allowance. |
| `activeSessionCount` | Reported active browser count. Track the browser IDs your application owns as well. |
| `totalCreditsBalanceUsd` | Its reported available credit balance. |
`rateLimit` is a legacy concurrency field. It does not tell you the HTTP request limit. Treat account information as a snapshot rather than a reservation for a future request. Several workers may read the same available capacity before any of them starts a browser.
Keys in the same project share its capacity and balance. Creating more keys does not create more browser slots. Free accounts can also have limits across their projects.
## How usage tiers work
For projects on current pricing, concurrency increases with the project's net settled lifetime Stripe payments:
| Lifetime qualifying spend | Concurrent sessions |
| ------------------------: | ------------------: |
| \$0 | 10 |
| \$200 | 50 |
| \$1,000 | 250 |
| \$5,000 | 500 |
| \$25,000 | 1,000 |
Payments count for the project that received them. Refunds and disputed payments do not contribute to qualifying spend. Signup credits and other credit grants are not qualifying purchases. Funding through other billing arrangements may follow different rules.
**Your higher concurrency allowance is preserved.** When a project participates in spend tiers, its limit takes the maximum of its applicable legacy-plan allowance, the current pricing floor, and its spend-tier allowance. An already-granted higher limit is not automatically lowered. For example, a legacy allowance of 250 remains 250 when the spend tier would grant 50.
Some active legacy or externally billed projects follow a different billing path. Lapsed or cancelling legacy plans can transition onto current pricing while keeping their higher grant. Use `concurrentSessionLimit` and the project’s Billing page as the source of truth. If the tier ladder is absent or differs from your expectations, contact support with your project ID before making a purchase solely to increase concurrency.
## Keep track of browser lifetime
A V4 **session** is a conversation. A **run** is one turn in that conversation. A **browser** is the live Chrome instance used for website interaction.
A browser can survive the run that created it and be reused by a later turn. A conversation can also have multiple browsers. Additional tabs in the same browser are not additional browser sessions.
After all runs in a conversation are inactive, V4 browsers become eligible for cleanup after approximately 20 minutes without recent run activity. Cleanup runs periodically, so this is not an exact shutdown deadline. V4 agent browsers also have a four-hour hard timeout.
If your application is finished with a browser, explicitly stop that browser rather than relying on idle cleanup. Only stop browser IDs your application owns and no longer needs. For a standalone browser, the stop request is:
```bash theme={null}
curl --fail-with-body -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"}'
```
Do not use this against a browser another active run or person still needs. Closing your local CDP connection is not a substitute for checking the cloud browser's lifecycle.
If your workflow requires human interaction, deliver the live-view URL from `browser.ready` while the run/browser is active. A conversation remaining in history does not mean its live browser is still available.
## Queue follow-ups within a conversation
Only one V4 run can be active in a session. Submitting a direct follow-up while it is busy returns **409**.
Choose the behavior you need:
* Wait for the active run to finish, then submit the next turn.
* Submit through the session message queue to process a later turn.
* Intentionally interrupt the active run when you want to change its current work.
The session message queue accepts up to **10 pending messages**. A full queue returns **429**. It is a queue for that conversation, not a project-wide batch queue or a reservation of browser capacity.
Keep a large batch in your application's own job queue. Admit a bounded number of jobs and leave the rest pending there.
## Budget HTTP requests separately
HTTP traffic passes through two separate limits: a shared per-IP edge limit
and your project's application limit. Raising one does not raise the other.
### Edge limits
On September 9, 2026, the standard edge (WAF) ceilings increased:
| Requests from one public source IP | Edge ceiling | Evaluation window |
| ---------------------------------- | --------------------: | --------------------------------: |
| General API traffic | 1,000 requests/second | 300,000 requests over 300 seconds |
| Selected individual status reads | 2,500 requests/second | 750,000 requests over 300 seconds |
These are approximate rates evaluated over a five-minute window, not a strict
per-second cap or a guaranteed burst allowance. Applications behind the same
NAT or proxy share the IP's budget. Account-specific edge rules and certain
special endpoints can have different limits.
### Project limits
The edge increase did **not** change the standard per-project application buckets:
| Requests | Default request budget |
| ----------------------------------------------------------------- | ------------------------------------------------: |
| Creates, lists, updates, deletes, event reads, and full run reads | 25 requests/second |
| Selected individual status reads | `max(25, 2 × stored concurrency)` requests/second |
The selected V4 polling endpoints include:
* `GET /api/v4/runs/{run_id}/status`
* `GET /api/v4/sessions/{session_id}`
* `GET /api/v4/browsers/{browser_session_id}`
`GET /api/v4/runs/{run_id}/events` and `GET /api/v4/runs/{run_id}` use the general bucket. Their budget does not automatically grow with your concurrency grant. Account overrides and other protections can differ from these defaults; use the response headers and error details when diagnosing throttling.
For example, a project with 500 stored concurrent sessions and no request-rate
override has a 1,000 RPS status-polling budget and a separate 25 RPS general
budget. All API keys in that project share those budgets. Creating a new key
does not create more request capacity.
The project limiter counts requests in **five-second windows**. A 25 RPS
budget therefore permits 125 requests per window. `X-RateLimit-Limit` reports
that window allowance, not requests per second: a value of `125` does not mean
125 RPS. `X-RateLimit-Remaining` is the window's remaining request count.
`X-RateLimit-Reset` is a relative number of seconds, not a Unix timestamp.
A project throttle includes `limit_rps` and `retry_after_seconds` in the JSON
body and normally sends `Retry-After: 5`. An edge throttle can instead send
`Retry-After: 300` without `limit_rps`. Honor the delay actually returned; a
five-second retry loop is inappropriate for a five-minute edge throttle.
Capture the response headers and body when asking support to identify which
limit applied. A missing limit header is not a promise of unlimited traffic.
If you only need the final result, use the SDK's status-based wait helper:
```python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
# run_id is the ID of a run your application already created.
result = client.runs.wait_for_completion(run_id)
print(result.status, result.result)
```
SDK 3.11.3 defaults to a two-second status interval and a four-hour client wait timeout. Reaching that timeout raises an exception locally; it does not cancel the server-side run. Inspect or cancel the existing run before deciding whether to submit replacement work.
### When you need events
Use the returned cursor to continue reading and process every page with `hasMore` before treating the event stream as caught up. After terminal status, drain the remaining event pages; do not exit solely because a separate status request completed.
Set a request budget across all event streams in the project. For example, polling events once per second for 50 runs would generate about 50 event requests per second, exceeding the standard general bucket before counting any creates or other requests. Polling each every five seconds would average about 10 event requests per second; pagination and other traffic still need headroom.
Spread poll times so that every worker does not send its request on the same clock tick. A per-project limiter in your application is more predictable than each worker independently retrying.
For SDK event waits, set `wait_for_event(..., interval=3)` in Python or
`waitForEvent(..., { interval: 3_000 })` in TypeScript. This also works on SDK
3.11.3, whose event-wait default is one second. One hundred waits at a
three-second interval average about 33 event requests per second, exceeding
the standard 25 RPS general budget. For that workload, explicitly use a
five-second interval (20 event requests per second) or a shared request
scheduler, leaving headroom for creates, pagination, and other general calls.
The event-wait helper pauses between pages too. For an old run with a large
event history, start from a known cursor or implement paginated reads within
your shared request budget; a longer interval also makes backlog traversal slower.
## Diagnose errors before retrying
| Response | Likely meaning | What to do |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| 429 with `error: "rate_limited"` and `limit_rps` | Project HTTP request budget exhausted | Honor `Retry-After`/`retry_after_seconds`; reduce or stagger requests across the project. |
| 429 with `error: "rate_limited"`, no `limit_rps`, and `Retry-After: 300` | Edge request protection | Back off for the returned delay; check bursts and traffic sharing the same public IP. |
| 429 with a concurrent-session message in `detail` | Active session allowance reached | Inspect capacity, stop unneeded browsers, and reduce admitted jobs. |
| 429 with a queue-full message in `detail` | This conversation has 10 pending messages | Wait for its queue to drain or cancel an appropriate pending message. |
| 409 with an active-run message | Conversation is already busy | Wait, queue, or intentionally interrupt. |
| 402 with `detail.code: "api_key_monthly_spend_limit_reached"` | This key's monthly soft spending cap was reached | Check the cap and recorded spend in the current UTC month. |
| Other 402 | May be insufficient project balance or another payment condition | Inspect the complete response and the API key's project. |
The SDK retries some transient responses, including 429, but the default is only three retries. SDK 3.11.3 uses exponential delays of roughly 1, 2, and 4 seconds and does not itself apply `Retry-After` or jitter. Add application-level scheduling for sustained workloads. Do not repeatedly submit a new billable run when the outcome of an earlier submission is uncertain.
SDK 3.11.3 errors expose the response status and body, but not response headers.
Use `retry_after_seconds` when the body includes it. To diagnose an edge throttle
or read its exact `Retry-After`, capture the raw HTTP response through your
application's HTTP instrumentation or a REST client.
API-key monthly spending caps are **soft limits**. They use a cached spend snapshot; already-running or simultaneous work can finish above the cap. They do not replace project balance management or per-run cost controls. BYOK provider charges also remain separate from Browser Use charges.
## A practical batch-processing pattern
1. Resolve the key's project, available balance, and current capacity.
2. Keep jobs in a durable application queue and admit a bounded number of workers. Leave room for browsers used elsewhere in the project and additional browsers created by agents.
3. Track job ID, session ID, run ID, and browser IDs separately.
4. Use cheap status polling for result-only jobs; share one request budget across event streams and general API operations.
5. Handle busy-session, queue, rate, capacity, and spending errors according to their cause.
6. Record terminal outcomes, release unneeded browsers, and retry only when the job's previous outcome is understood.
When contacting support, include the UTC time window, endpoint, status code, redacted response body, project ID, SDK version, run/session/browser IDs, and approximate request rate and concurrency. Do not send API keys, proxy passwords, or saved browser credentials.
See also [Billing and credits](/cloud/guides/billing), [Sessions](/cloud/agent/sessions), and [Observability](/cloud/agent/observability).
# MCP Server
Source: 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.
```
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 theme={null}
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 theme={null}
{
"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 theme={null}
{
"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 theme={null}
{
"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. |
# Sync local and cloud cookies
Source: https://docs.browser-use.com/cloud/guides/profile-sync
Sync a local login, then use it in an API V4 run.
Run the profile sync helper:
```bash theme={null}
export BROWSER_USE_API_KEY=your_key
curl -fsSL https://browser-use.com/profile.sh | sh
```
Choose the accounts to sync, then use the returned profile ID:
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
client = BrowserUse()
run = client.runs.create(
"Check my LinkedIn messages",
browser_settings={"profileId": "YOUR_PROFILE_ID"},
)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const run = await client.runs.create({
task: "Check my LinkedIn messages",
model: "grok-4.5",
browserSettings: {
profileId: "YOUR_PROFILE_ID",
proxyCountryCode: "us",
},
});
```
The profile supplies cookies and local storage without putting credentials in
the prompt. Re-sync when the site's login expires.
A profile ID belongs to a project. Use an API key from that project, and do not
substitute a workspace ID or conversation/session ID. Reusing a session may
reuse its current browser; to apply a newly synced profile, create a fresh
browser or conversation with that profile.
Cookie sync does not import a password manager or guarantee that the target
site accepts a moved login. Sites can expire cookies, bind sessions to a device
or network, or require another verification step. Use a stable proxy location
when the site's security policy depends on location.
# Secrets
Source: https://docs.browser-use.com/cloud/guides/secrets
Pass run-scoped credentials to API V4 with secret bindings.
## API V4: inline secret bindings
Use `secret_bindings` / `secretBindings` with the explicit V4 client. Give each
credential an alias and the hostnames where it may be typed. Refer to the alias
in the task, not the credential value.
Aliases avoid putting raw credentials in the task text. They do not guarantee
that the agent cannot see the value: once typed, it is accessible to the page
and to an agent with browser/CDP access.
This example fills a password field and stops before submitting. Replace the
example portal URL and provide `PORTAL_PASSWORD` through your application's
secret store or environment. Do not commit the value or print the request body.
```python Python theme={null}
import os
from browser_use_sdk.v4 import BrowserUse
with BrowserUse() as client:
run = client.runs.create(
"Open https://portal.example.com/login, enter portal_password "
"in the password field, and stop before submitting",
secret_bindings=[
{
"alias": "portal_password",
"source": {
"type": "inline",
"value": os.environ["PORTAL_PASSWORD"],
},
"allowedDomains": ["portal.example.com"],
}
],
)
print(run.id)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const password = process.env.PORTAL_PASSWORD;
if (!password) throw new Error("Set PORTAL_PASSWORD before starting a run");
const client = new BrowserUse();
const run = await client.runs.create({
task: "Open https://portal.example.com/login, enter portal_password in the password field, and stop before submitting",
secretBindings: [
{
alias: "portal_password",
source: { type: "inline", value: password },
allowedDomains: ["portal.example.com"],
},
],
});
console.log(run.id);
```
* `allowedDomains` takes bare hostnames, not URLs, ports, paths, or wildcards.
A hostname also covers its subdomains. Use the narrowest host that works.
* These domains restrict where that credential may be typed. They are **not** a
browser-wide navigation or network allowlist.
* Bindings belong to one run. A follow-up that needs the same credential must
pass the binding again, even when it reuses the same session.
* Use a separate alias for each credential field. Up to 10 bindings are allowed
per run, with up to 10 allowed domains per binding.
* Aliases contain 1–64 lowercase letters, digits, hyphens, or underscores and
start with a letter or digit.
* The inline limit is 4,096 bytes **after JSON encoding for encryption**,
including its wrapper and escaping. It is not a 4,096-character allowance;
non-ASCII characters, quotes, and backslashes consume extra space.
For credentials stored in a connected vault, use a
[1Password field binding](/cloud/guides/1password#bind-individual-fields-in-a-v4-run).
See the [Create run reference](/cloud/api-v4/runs/create-run) for the current
request schema.
## Legacy API V2
The examples below use the default V2 client and its `secrets` /
`allowed_domains` fields. Do not pass those fields to `v4.runs.create()`; use
the secret bindings above. Keep this recipe only for an existing V2 integration.
Pass credentials to the agent scoped by domain.
```python Python theme={null}
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"],
)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk";
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"],
},
);
```
Use `allowed_domains` to restrict the agent to specific domains. Supports wildcards: `example.com`, `*.example.com`.
For SSO/OAuth redirects, include all domains in the auth flow:
```python Python theme={null}
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"],
)
```
```typescript TypeScript theme={null}
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"],
},
);
```
# Troubleshooting
Source: https://docs.browser-use.com/cloud/guides/troubleshooting
Diagnose account, browser, file, and integration problems before retrying.
## My API key has credits, but a request fails
Read `GET /api/v2/billing/account` with the same key. It also works for V4
users. Confirm the `projectId`, balance, and `concurrentSessionLimit` against
the project selected in the dashboard. Keys in different projects do not
share credits. A pay-as-you-go account can legitimately have `planInfo: null`.
Read the complete error response before changing billing settings:
| Response | What to check |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401 | Send `X-Browser-Use-API-Key`; do not put the API key in a Bearer header. |
| 402 | Distinguish insufficient project credits from `api_key_monthly_spend_limit_reached`. A top-up does not raise a key's spending cap. |
| 404 for a profile/workspace/session | Check the ID type, API version, and owning project. An ID visible in one dashboard project may be inaccessible to another key. |
| 409 | Check the detail: a busy conversation and a workspace-file overwrite conflict require different actions. |
| 413 | Check per-file and workspace limits. Retrying the same oversized payload does not make it smaller. |
| 422 | Compare the request with the reference for that exact endpoint. Standalone-browser fields and V4 run `browserSettings` differ. |
| 429 | Distinguish HTTP request-rate limits from occupied browser slots. Reduce polling for the first; stop unneeded browsers or reduce concurrent work for the second. |
See [billing](/cloud/guides/billing), [concurrency](/cloud/guides/concurrency),
and [workspaces](/cloud/agent/workspaces) for the relevant limits and remedies.
## Why am I charged when I bring my own key or proxy?
BYOK means your model provider bills its tokens. Browser Use still charges
orchestration plus browser and network usage. A custom proxy has its own
provider charges and does not remove Browser Use browser/network charges.
A Claude or ChatGPT consumer subscription is not a provider API key.
See the [billing breakdown](/cloud/guides/billing).
## The dashboard works, but the same integration does not
Check the API key's project, selected model, profile, proxy settings, and API
version. A connected integration in the dashboard does not automatically
grant every API run access to it. Pass the documented run-level bindings or
grants. For example, see [1Password](/cloud/guides/1password) and
[Secrets](/cloud/guides/secrets).
A site can also challenge a fresh browser even if it accepts an existing
logged-in browser. Reuse an appropriate [profile](/cloud/guides/profile-sync),
and compare the actual proxy configuration before changing the agent prompt.
## The browser is idle, but usage or a concurrency slot remains
A completed agent run can leave its browser available for follow-ups. Closing
your SDK client or disconnecting CDP does not stop the managed browser. Stop
an unneeded browser with `PATCH /api/v4/browsers/{id}` and
`{"action":"stop"}`. See [browser lifetime](/cloud/guides/concurrency#keep-track-of-browser-lifetime).
If a local wait times out, the server may still be running. Fetch the existing
run's status before creating a replacement, especially if it might already
have submitted a form or performed another external action.
## I cannot find a file or recording
Wait for the run to finish before listing generated files, and use its V4
workspace ID. Request a fresh download URL if an old one expired. A session's
conversation, workspace files, and browser profile are different resources.
See [Workspaces and files](/cloud/agent/workspaces).
Recordings are off by default for API browsers. Enable recording when
creating the browser, stop it when finished, and allow time for processing.
A live preview is not a stored video. See [Live preview and recording](/cloud/browser/live-preview).
## Does a new browser guarantee a new IP or a CAPTCHA bypass?
No. Selecting a proxy country chooses a location; it does not promise a
particular city or a unique IP on every launch. Websites can still block
requests or require human verification. Let the automatic solver work before
clicking or refreshing a challenge. See [proxies](/cloud/browser/proxies) and
[CAPTCHA handling](/cloud/browser/captcha-handling).
## What should I send support?
Include the API version and endpoint, run/session/browser/workspace IDs that
apply, the project ID, timestamp with time zone, SDK version, and the complete
redacted error response. State what you expected and what actually happened.
For billing, include the payment reference and selected project; for a file
problem, include the filename, size, and the operation that failed.
Do not send API keys, passwords, custom-proxy credentials, session cookies,
or active CDP/live-view URLs. A screenshot alone often omits the identifier
needed to trace a request.
# Webhooks
Source: https://docs.browser-use.com/cloud/guides/webhooks
Receive V2 task and V3 session notifications and verify the webhook signature.
Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks).
## Events
| Event | API | When |
| -------------------------- | --------- | ----------------------------------------------------- |
| `agent.task.status_update` | V2 | Task status changes (`running`, `idle`, or `stopped`) |
| `session.status.update` | V3 | Session status changes; inspect `payload.status` |
| `test` | Dashboard | Webhook test ping |
Match the event type to the API version you use. For V4 run monitoring, see [Observability](/cloud/agent/observability) and [polling guidance](/cloud/guides/concurrency#budget-http-requests-separately).
## Payload
A V2 task event includes `task_id`, `session_id`, `status`, and task `metadata`:
```json theme={null}
{
"type": "agent.task.status_update",
"timestamp": "2026-09-09T12:00:00Z",
"payload": {
"task_id": "task_abc123",
"session_id": "session_xyz",
"status": "idle",
"metadata": {}
}
}
```
A V3 event uses the session's `id` as `payload.session_id`. `output` is included when available:
```json theme={null}
{
"type": "session.status.update",
"timestamp": "2026-09-09T12:00:00Z",
"payload": {
"session_id": "session_xyz",
"status": "idle",
"output": "The agent's result"
}
}
```
Status-change events are not all completion events. Check the status before continuing your workflow.
## Signature verification
Every webhook request includes two headers:
* `X-Browser-Use-Signature`: lowercase hexadecimal HMAC-SHA256 signature.
* `X-Browser-Use-Timestamp`: Unix timestamp in seconds when this delivery was sent.
The signed message is the UTF-8 encoding of `{header_timestamp}.{canonical_payload}`. The canonical payload is exactly Python's `json.dumps(payload, sort_keys=True, separators=(',', ':'), ensure_ascii=True)`, applied to the **entire event**, including its `type`, `timestamp`, and `payload`.
This is not a signature over the raw HTTP body. The sender serializes the HTTP body separately. Non-ASCII characters are escaped in the signed representation: for example, `München` becomes `M\u00fcnchen`.
JavaScript `JSON.stringify`, even after sorting an object's keys, does not implement this format. Unicode escaping, integer-like object keys, and number formatting can differ from Python. Do not use a generic JavaScript JSON canonicalizer or raw-body HMAC verifier for this contract. The Python verifier below matches the current sender; other implementations must match its serialization exactly and preserve number representations when parsing.
Save this as `webhook_verify.py`:
```python theme={null}
import hashlib
import hmac
import json
import re
import time
def verify_webhook(body: bytes, signature: str, timestamp: str, secret: str) -> bool:
if not isinstance(timestamp, str) or not re.fullmatch(r"[0-9]{1,12}", timestamp):
return False
if abs(time.time() - int(timestamp)) > 300:
return False
if not isinstance(signature, str) or not re.fullmatch(r"[0-9a-f]{64}", signature):
return False
try:
payload = json.loads(body)
if not isinstance(payload, dict):
return False
canonical = json.dumps(
payload, sort_keys=True, separators=(",", ":"),
ensure_ascii=True, allow_nan=False,
)
except (ValueError, TypeError, UnicodeError):
return False
message = f"{timestamp}.{canonical}".encode("utf-8")
expected = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
```
The five-minute timestamp window limits replay, but does not make delivery unique. Make your business action idempotent so a duplicate callback cannot process the same result twice. Keep your server clock synchronized.
## Example: FastAPI webhook handler
Install `fastapi` and `uvicorn`, put this `app.py` beside `webhook_verify.py`, set `WEBHOOK_SECRET` to the signing secret from your webhook settings, and run `uvicorn app:app --port 3000`.
```python theme={null}
import json
import os
from fastapi import FastAPI, HTTPException, Request
from webhook_verify import verify_webhook
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", "")
if not verify_webhook(body, signature, timestamp, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid webhook signature or timestamp")
event = json.loads(body)
if event.get("type") in {"agent.task.status_update", "session.status.update"}:
data = event["payload"]
# Persist or enqueue the verified event for your application to process.
# Deduplicate before applying business side effects.
print(f"Session {data['session_id']} is now {data['status']}")
return {"status": "ok"}
```
Return promptly after durably accepting the event. The sender has a 10-second request timeout and retries temporary failures, so slow processing can cause duplicate deliveries. HTTP 400, 401, 403, 404, and 410 are not retried.
For local development, expose your local server with a tool such as [ngrok](https://ngrok.com): `ngrok http 3000`. Set the resulting `/webhook` URL in the dashboard and use its test action before starting tasks.
# x402 (pay-per-request)
Source: 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.
[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 \$1 of USDC on Base in a wallet you control. The Claude Code quickstart below walks you through everything from scratch.
**Four ways to start, ranked by laziness:**
One command. Claude does the wallet setup, funding walkthrough, and
verification for you.
AgentCash or Coinbase's Agentic Wallet. Your coding agent gets a wallet directly and
pays as it goes.
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 theme={null}
npx skills add https://github.com/browser-use/browser-use --skill x402
```
Then in Claude Code:
```
> /x402
```
Claude walks you through creating or importing a wallet outside the chat, loading `BROWSER_USE_X402_PRIVATE_KEY` before Claude starts, installing the SDK, and running a verification task.
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 theme={null}
pip install "browser-use-sdk[x402]"
```
```bash TypeScript theme={null}
npm install browser-use-sdk @x402/fetch @x402/evm viem
```
```python Python theme={null}
import asyncio
from browser_use_sdk.v3 import AsyncBrowserUse
async def main():
client = AsyncBrowserUse(x402_max_payment_usd=1.0)
result = await client.run(
"Go to example.com and tell me the heading.",
max_cost_usd=0.75,
)
print(result.output)
asyncio.run(main())
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse({ x402MaxPaymentUsd: 1 });
const result = await client.run("Go to example.com and tell me the heading.", {
maxCostUsd: 0.75,
});
console.log(result.output);
```
Or set `BROWSER_USE_X402_PRIVATE_KEY` in your env, and skip the constructor arg entirely:
```python Python theme={null}
client = AsyncBrowserUse() # auto-detects from env
```
```typescript TypeScript theme={null}
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 theme={null}
import asyncio
import os
from x402 import max_amount, 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(os.environ["BROWSER_USE_X402_PRIVATE_KEY"])),
policies=[max_amount(1_000_000)],
)
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**
* **SDK top-up:** `$1.00` USDC by default, with a configurable hard per-payment cap
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.
No wallet yet? Jump to [Wallet setup](#wallet-setup) below.
## Pricing and credits
The SDK first authenticates requests with a free, single-use wallet signature. If
the wallet project does not exist yet or has insufficient credits, the backend
marks the response as requiring a top-up, and the SDK makes one x402 payment. The
default SDK cap is `$1.00` USDC per payment.
**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.
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 from the dashboard, 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 theme={null}
import asyncio
import os
from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse(
api_key=os.environ["BROWSER_USE_API_KEY"],
x402_max_payment_usd=1.0, # hard cap for each top-up
base_url="https://x402.api.browser-use.com/api/v3",
)
async def main():
result = await client.run("...", max_cost_usd=0.75)
print(result.output)
asyncio.run(main())
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse({
apiKey: process.env.BROWSER_USE_API_KEY,
x402MaxPaymentUsd: 1,
baseUrl: "https://x402.api.browser-use.com/api/v3",
});
const result = await client.run("...", { maxCostUsd: 0.75 });
```
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 theme={null}
import asyncio
import os
from browser_use_sdk.v3 import get_wallet_balance
async def main():
balance = await get_wallet_balance(os.environ["BROWSER_USE_X402_PRIVATE_KEY"])
print(balance["total_credits_usd"])
asyncio.run(main())
```
```typescript TypeScript theme={null}
import { getWalletBalance } from "browser-use-sdk/v3";
const balance = await getWalletBalance(process.env.BROWSER_USE_X402_PRIVATE_KEY!);
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. If your Browser Use credit balance needs a top-up,
the SDK authorizes up to your configured payment cap; otherwise it proceeds
without moving wallet funds.
A bit more detail:
1. Your code makes a request (e.g. "run this task").
2. The SDK signs a free, request-bound wallet authentication message.
3. If the server explicitly requests a top-up, the SDK signs one capped payment and retries.
4. Coinbase moves the USDC on-chain, and we add the same amount to your project's credit balance.
5. Status polls use free wallet authentication while the task runs.
## 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 theme={null}
import os
from x402 import max_amount, 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
key = os.environ["BROWSER_USE_X402_PRIVATE_KEY"]
x402 = x402Client()
register_exact_evm_client(
x402,
EthAccountSigner(Account.from_key(key)),
policies=[max_amount(1_000_000)],
)
client = AsyncBrowserUse(x402=x402, x402_private_key=key)
```
```typescript TypeScript theme={null}
import { x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import { BrowserUse } from "browser-use-sdk/v3";
const key = process.env.BROWSER_USE_X402_PRIVATE_KEY! as `0x${string}`;
const x402 = new x402Client();
x402.register("eip155:*", new ExactEvmScheme(privateKeyToAccount(key)));
x402.registerPolicy((_version, requirements) =>
requirements.filter(({ amount }) => BigInt(amount) <= 1_000_000n),
);
const client = new BrowserUse({ x402, x402PrivateKey: key });
```
## Troubleshooting
Two likely causes:
* **Wallet has no USDC on Base.** Check your balance. If empty, top it up.
* **Your HTTP client isn't x402-aware.** Plain `requests` / `fetch` just sees a 402 and stops; it doesn't know how to read the payment instructions and sign a payment. Use the SDK (which handles this automatically), or wrap your HTTP client with one of the [x402 client libraries](https://github.com/coinbase/x402#all-available-reference-sdks).
You haven't installed the optional x402 deps. Run `pip install
"browser-use-sdk[x402]"` (Python) or `npm install @x402/fetch @x402/evm viem`
(TypeScript).
We verified your payment request but couldn't credit your project, so we
deliberately did not settle on-chain. No USDC was moved, so just retry. This
is rare.
Wait a few seconds. Settlement and credit grant happen in the same request,
but the response may be sent before the credit grant fully commits. If credits
still show `$0` after a few minutes, contact support with your wallet address.
(Conversely, if a payment settles but the request itself then fails, we
automatically reclaim the credits so you aren't charged for nothing.)
`eip155:8453` is Base mainnet; `eip155:84532` is Base Sepolia testnet. Browser Use Cloud only accepts mainnet. Withdrawing USDC to Sepolia from Coinbase is **not** the same as Base mainnet, even though both use the same wallet address.
## Related
* [x402 protocol spec](https://www.x402.org)
* [Agent wallets quickstart](/cloud/guides/x402-agent-wallets) — pay from AgentCash or a Coinbase Agentic Wallet
* [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)
# x402 agent wallets
Source: https://docs.browser-use.com/cloud/guides/x402-agent-wallets
Pay for Browser Use Cloud from an agent-native wallet: AgentCash (local wallet, MCP) or Coinbase's Agentic Wallet (hosted wallet, CLI).
Give your agent a wallet with a few dollars of USDC in it, and it can run Browser Use tasks and pay as it goes. Pick a wallet:
A wallet that lives on your machine, built for coding agents: Claude Code,
Cursor, Codex.
A wallet Coinbase hosts for you. Sign in with email, fund with Apple Pay or
card, pay from the terminal.
Payments to Browser Use are **credit top-ups, not per-call fees**: each x402
payment (\$5 default, \$1 minimum) buys prepaid credit for a project tied to
your wallet, and unused credit carries over. See [pricing and
credits](/cloud/guides/x402#pricing-and-credits).
## AgentCash quickstart
[AgentCash](https://agentcash.dev) gives your coding agent a wallet and the tools to spend from it: check prices, pay for API calls, watch the balance. The wallet is a file on your machine.
```bash theme={null}
npx agentcash install
```
The interactive installer detects your agent client. Or add it directly:
```bash theme={null}
claude mcp add agentcash --scope user -- npx -y agentcash@latest # Claude Code
codex mcp add agentcash -- npx -y agentcash@latest # Codex
npx agentcash install --client cursor # Cursor
```
The first run creates a wallet at `~/.agentcash/wallet.json`. Get its
deposit address and send it USDC on **Base mainnet** (\$5 covers the default
top-up; \$1 is the minimum):
```bash theme={null}
npx agentcash@latest accounts
```
Or just ask your agent — the `list_accounts` and `get_balance` tools do the
same thing.
Prompt your agent:
```text theme={null}
Use AgentCash to POST to https://x402.api.browser-use.com/api/v3/sessions
with body {"task": "Go to example.com and tell me the page title"}.
Pay the $1 minimum option, then poll the returned session id at
GET /api/v3/sessions/{id} until status is "stopped" and show me the output.
```
The `fetch` tool handles the whole 402 → sign → retry loop. Use
`check_endpoint_schema` first if you want to see the price and input schema
without paying.
## Coinbase Agentic Wallet quickstart
[Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/welcome) is a wallet Coinbase hosts for you, driven from the terminal with the [`awal`](https://www.npmjs.com/package/awal) CLI. Sign in with your email — no keys to manage — fund it with Apple Pay or a card, and pay for API calls with one command. The [agentic-wallet-skills](https://github.com/coinbase/agentic-wallet-skills) package teaches your coding agent the same commands.
```bash theme={null}
npx skills add coinbase/agentic-wallet-skills # optional: teach your agent
npx awal auth login you@example.com # sends an OTP to your email
npx awal auth verify 123456 # paste the code
npx awal status
```
```bash theme={null}
npx awal show # opens the wallet UI → Fund → Apple Pay / card / Coinbase
npx awal balance
```
Or send USDC on Base directly to the address from `npx awal address`.
```bash theme={null}
npx awal x402 details https://x402.api.browser-use.com/api/v3/sessions
```
shows our payment options (\$5 default / \$1 minimum, USDC on Base) and the
task input schema, without paying. Then:
```bash theme={null}
npx awal x402 pay https://x402.api.browser-use.com/api/v3/sessions \
-X POST -d '{"task": "Go to example.com and tell me the page title"}' \
--max-amount 5000000 --json
```
`--max-amount` is in atomic USDC units: `1000000` = \$1.00.
The response contains the session `id`. Poll
`https://x402.api.browser-use.com/api/v3/sessions/{id}` until `status` is
`"stopped"`, then read `output`.
## Which one?
| | AgentCash | Coinbase Agentic Wallet |
| --------- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
| Wallet | Local file, self-custody | Coinbase-hosted, email OTP |
| Funding | Send USDC on Base to your address | Onramp (Apple Pay / card / bank) or direct USDC |
| Interface | MCP tools + CLI | CLI (+ skill for agents, [MCP variant](https://docs.cdp.coinbase.com/agentic-wallet/mcp/welcome)) |
| Best for | Autonomous coding agents | Humans who want fiat funding; CDP-ecosystem apps |
Building a production app instead of driving a CLI? Use
[`CdpX402Client`](https://docs.cdp.coinbase.com/x402/quickstart-for-buyers) from
the CDP SDK (a CDP-managed server wallet) or [bring your own x402
client](/cloud/guides/x402#advanced-bring-your-own-x402-client).
## Related
* [x402 overview](/cloud/guides/x402) — SDK, Claude Code skill, raw HTTP, pricing, troubleshooting
* [AgentCash docs](https://agentcash.dev) · [agentcash-skills](https://github.com/Merit-Systems/agentcash-skills)
* [Agentic Wallet CLI quickstart](https://docs.cdp.coinbase.com/agentic-wallet/cli/quickstart) (Coinbase)
# Agent (v2)
Source: https://docs.browser-use.com/cloud/legacy/agent
V2 agent models and file handling.
## Models
| Model | API String | Cost per Step |
| ------------------------- | ---------------------------- | ------------- |
| Browser Use 2.0 (default) | `browser-use-2.0` | \$0.006 |
| O3 | `o3` | \$0.03 |
| Gemini Flash Latest | `gemini-flash-latest` | \$0.0075 |
| Gemini Flash Lite Latest | `gemini-flash-lite-latest` | \$0.005 |
| Claude Sonnet 4.5 | `claude-sonnet-4-5-20250929` | \$0.05 |
| Claude Sonnet 4.6 | `claude-sonnet-4.6` | \$0.05 |
Pass `llm` explicitly to select a model:
```python Python theme={null}
result = await client.run("...", llm="browser-use-2.0")
```
```typescript TypeScript theme={null}
const result = await client.run("...", { llm: "browser-use-2.0" });
```
***
## Files
Upload images, PDFs, documents, and text files (10 MB max) to sessions, and download output files from completed tasks.
### Upload a file
Get a presigned URL, then upload via POST.
```python Python theme={null}
import httpx
from browser_use_sdk import AsyncBrowserUse
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,
)
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"})
result = await client.run("Summarize the uploaded PDF", session_id=session.id)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk";
import { readFileSync } from "fs";
const client = new BrowserUse();
const session = await client.sessions.create();
const upload = await client.files.sessionUrl(session.id, {
fileName: "input.pdf",
contentType: "application/pdf",
sizeBytes: 1024,
});
await fetch(upload.url, {
method: "POST",
body: readFileSync("input.pdf"),
headers: { "Content-Type": "application/pdf" },
});
const result = await client.run("Summarize the uploaded PDF", { sessionId: session.id });
```
Presigned URLs expire after 120 seconds. Max file size: 10 MB.
### Download task output files
```python Python theme={null}
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
```
```typescript TypeScript theme={null}
const result = await client.tasks.get(taskId);
for (const file of result.outputFiles) {
const output = await client.files.taskOutput(taskId, file.id);
console.log(output.downloadUrl);
}
```
***
## Streaming steps
Use `async for` to yield steps as the agent works.
```python Python theme={null}
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(run.result.output) # final result after iteration
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk";
const client = new BrowserUse();
const run = client.run("Find the most upvoted post on Reddit r/technology today");
for await (const step of run) {
console.log(`Step ${step.number}: ${step.nextGoal}`);
console.log(` URL: ${step.url}`);
}
console.log(run.result?.output); // final result after iteration
```
***
## Key parameters
| Parameter | Type | Description |
| -------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `task` | `str` | What you want the agent to do. 1-50,000 characters. |
| `llm` | `str` | Model override. Default: Browser Use 2.0. |
| `output_schema` / `schema` | Pydantic / Zod | Schema for structured output. |
| `session_id` | `str` | Reuse an existing session. Omit for auto-session. |
| `start_url` | `str` | Initial page URL. Saves steps — send the agent directly there. |
| `secrets` | `dict` | Domain-specific credentials. See [Authentication](/cloud/guides/authentication). |
| `allowed_domains` | `list[str]` | Restrict agent to these domains only. |
| `session_settings` | `SessionSettings` | Proxy, profile, browser config. See [Profiles](/cloud/guides/authentication). |
| `flash_mode` | `bool` | Faster but less careful navigation. |
| `thinking` | `bool` | Enable legacy agent thinking behavior. This does not set model reasoning depth. |
| `thinkingLevel` (REST) | `str` | Provider reasoning depth. The generated SDK parameter is pending; see [Thinking levels](/cloud/agent/thinking-levels). |
| `vision` | `bool \| str` | Enable screenshots for the agent. |
| `highlight_elements` | `bool` | Highlight interactive elements on the page. |
| `system_prompt_extension` | `str` | Append custom instructions to the system prompt. |
| `judge` | `bool` | Enable quality judge to verify output. |
| `skill_ids` | `list[str]` | Skills the agent can use during the task. |
| `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
Generate shareable URLs for agent sessions using the v2 API.
Generate a public URL that anyone can open to watch the entire agent session — no API key needed. Useful for sharing with teammates, stakeholders, or embedding in dashboards.
```python Python theme={null}
share = await client.sessions.create_share(session.id)
print(share.share_url)
```
```typescript TypeScript theme={null}
const share = await client.sessions.createShare(session.id);
console.log(share.shareUrl);
```
# Skills
Source: https://docs.browser-use.com/cloud/legacy/skills
Retired. Existing skills still run; new skills are no longer created.
Skills are retired. You can no longer create or refine a skill: `skills.create`,
`skills.refine`, and manual recording return `410 Gone`. Existing skills can still
be viewed and run, and Marketplace skills can still be cloned and executed.
For new work, use [rerunnable scripts](/cloud/agent/scripts). A Cloud agent learns the
task once and saves the working code in its workspace, so every later run reuses it.
## Run an existing skill
```python Python theme={null}
result = await client.skills.execute(
skill_id,
parameters={"X": 10},
)
print(result)
```
```typescript TypeScript theme={null}
const result = await client.skills.execute(skillId, {
parameters: { X: 10 },
});
console.log(result);
```
## Marketplace
Browse, clone, and run community-created skills. Creating new skills is no longer available.
```python Python theme={null}
skills = await client.marketplace.list()
my_skill = await client.marketplace.clone(skill_id)
result = await client.marketplace.execute(skill_id, parameters={...})
```
```typescript TypeScript theme={null}
const skills = await client.marketplace.list();
const mySkill = await client.marketplace.clone(skillId);
const result = await client.marketplace.execute(skillId, { parameters: { ... } });
```
See [Pricing](https://browser-use.com/pricing) for skill costs.
# Quick start
Source: https://docs.browser-use.com/cloud/quickstart
Run a hosted agent or launch a cloud browser.
Give an agent a task and get the result.
Launch a cloud browser and connect to it from your code.
Both Cloud paths use API V4. Choose whether Browser Use drives the browser or
your Playwright/Puppeteer code connects directly over CDP. For a local agent,
use the [open-source library](/open-source/quickstart).
Get an [API key](https://cloud.browser-use.com/settings?tab=api-keys\&new=1) and
export it:
```bash theme={null}
export BROWSER_USE_API_KEY=your_key
```
## Install the SDK
Use Python 3.10 or newer for the Python SDK. Skip installation if you use curl.
```bash Python theme={null}
pip install --upgrade browser-use-sdk playwright
```
```bash TypeScript theme={null}
npm install browser-use-sdk@latest puppeteer-core
```
## Run Browser Use Agents
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
with BrowserUse() as client:
run = client.runs.create("Find the top Hacker News story")
result = client.runs.wait_for_completion(run.id)
print(result.result)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
const client = new BrowserUse();
const run = await client.runs.create({
task: "Find the top Hacker News story",
});
const result = await client.runs.waitForCompletion(run.id);
console.log(result.result);
```
```bash curl theme={null}
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"}'
```
## Use Browser Infrastructure
These examples connect to the managed browser's existing context and stop it
in `finally`, including when connecting or navigating fails.
```python Python theme={null}
from browser_use_sdk.v4 import BrowserUse
from playwright.sync_api import sync_playwright
with BrowserUse() as client:
session = client.browsers.create(proxy_country_code="us")
try:
if not session.cdp_url:
raise RuntimeError("The browser did not return a CDP URL")
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.cdp_url)
context = browser.contexts[0]
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://example.com")
print(page.title())
finally:
client.browsers.stop(session.id)
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v4";
import puppeteer from "puppeteer-core";
const client = new BrowserUse();
const session = await client.browsers.create({ proxyCountryCode: "us" });
try {
if (!session.cdpUrl) throw new Error("The browser did not return a CDP URL");
const browser = await puppeteer.connect({ browserWSEndpoint: session.cdpUrl });
try {
const context = browser.defaultBrowserContext();
const page = (await context.pages())[0] ?? await context.newPage();
await page.goto("https://example.com");
console.log(await page.title());
} finally {
await browser.disconnect();
}
} finally {
await client.browsers.stop(session.id);
}
```
Disconnecting CDP does not stop the managed browser. Use
`client.browsers.stop(session.id)` as above, or call
`PATCH /api/v4/browsers/{id}` with `{"action":"stop"}`.
For curl and additional connection options, follow the
[Browser Infrastructure quickstart](/cloud/browser/quickstart#launch-and-connect).
Give your coding agent the compact API V4 context.
# Chat UI
Source: 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.
Clone and run in minutes. Next.js + Browser Use SDK v3.
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.
The app has two pages:
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.
All SDK calls live in a single file: `src/lib/api.ts`.
## Setup
```typescript api.ts theme={null}
import { BrowserUse } from "browser-use-sdk/v3";
// 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 });
```
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.
***
## 1. Create a session
```typescript actions.ts theme={null}
"use server";
import { client } from "./api";
export async function createSession() {
const session = await client.sessions.create({
keepAlive: true,
enableRecording: true,
});
return { id: session.id, liveUrl: session.liveUrl, status: session.status };
}
```
* **`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 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:
```typescript page.tsx theme={null}
async function handleSend(message: string) {
const session = await createSession();
router.push(
`/session/${session.id}?liveUrl=${encodeURIComponent(session.liveUrl)}&task=${encodeURIComponent(message)}`
);
}
```
***
## 2. Stream messages with `for await`
Instead of polling `sessions.get()` and `sessions.messages()` separately, use `client.run()` — it streams messages and resolves when the task completes:
```typescript session-context.tsx theme={null}
const streamTask = useCallback(async (task: string) => {
const run = client.run(task, { sessionId });
for await (const msg of run) {
setMessages((prev) => [...prev, msg]);
}
// Iterator done — task reached terminal state
setSession(run.result);
}, [sessionId]);
```
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.
Wire it up in a `useEffect` to auto-run the initial task from URL params:
```typescript session-context.tsx theme={null}
useEffect(() => {
if (!initialTask) return;
sendMessage(initialTask);
}, []);
```
***
## 3. Follow-up tasks
Follow-ups call the same `streamTask` function — the stream already includes the user message, so no optimistic insert is needed:
```typescript session-context.tsx theme={null}
const sendMessage = useCallback(async (task: string) => {
await streamTask(task);
}, [streamTask]);
```
The SDK auto-sets `keepAlive: true` when targeting an existing session, so follow-up tasks work without extra config.
***
## 4. Recording
Fetch the MP4 URL after the session ends (recording was enabled in step 1):
```typescript session-context.tsx theme={null}
useEffect(() => {
if (!isTerminal) return;
client.sessions.waitForRecording(sessionId).then((urls) => {
if (urls.length) setRecordingUrls(urls);
});
}, [isTerminal, sessionId]);
```
`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.
***
## 5. Stop a task
```typescript actions.ts theme={null}
export async function stopTask(id: string) {
await client.sessions.stop(id, { strategy: "task" });
}
```
Using `strategy: "task"` stops only the current task, keeping the session alive for follow-ups.
***
## 6. Session page
The session page consumes everything through a context provider:
```typescript session/[id]/page.tsx theme={null}
function SessionPage() {
const { session, turns, isBusy, isTerminal, recordingUrls, sendMessage, stopTask } =
useSession();
return (
{/* Chat column */}
{/* Live browser view — liveUrl available from session creation */}
);
}
```
***
## 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 |
# Grow Therapy provider search
Source: https://docs.browser-use.com/cloud/tutorials/grow-therapy-compare
Search Grow Therapy for therapists by location, insurance, and specialty — with cached reruns.
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
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 a successful search flow for reruns with different locations and specialties
***
## Setup
```python Python theme={null}
import asyncio
import json
from pydantic import BaseModel
from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
```
```typescript TypeScript theme={null}
import { BrowserUse } from "browser-use-sdk/v3";
import { z } from "zod";
const client = new BrowserUse();
```
## 1. Define the output schema
```python Python theme={null}
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 theme={null}
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 theme={null}
workspace = await client.workspaces.create(name="grow-therapy-search")
```
```typescript TypeScript theme={null}
const workspace = await client.workspaces.create({ name: "grow-therapy-search" });
```
## 3. Search for providers
This tutorial uses **API V3** automatic script caching. Mark changing values with `@{{value}}`; plain `{{value}}` does not activate caching. Keep the rest of the task text and the workspace unchanged so later requests can find the same cached script. The first successful generation uses the agent and incurs normal usage charges.
```python Python theme={null}
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 theme={null}
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 a successful first run caches the search flow, change only the marked values. A successful cached script execution avoids agent LLM inference, but browser and network usage still apply. Cache misses, validation, and automatic repair can invoke the agent and incur LLM charges; a rerun is not a guarantee of zero LLM cost.
```python Python theme={null}
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 theme={null}
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 or cache miss | Agent creates and validates the cached flow | Normal agent, browser, and network charges |
| Successful cached execution | Script reruns with new parameters | No agent LLM inference for script execution; browser and network charges apply |
| Validation or repair | Agent validates or repairs a flow that needs attention | LLM usage plus browser and network charges |
Therapy platforms have dynamic UIs that can change frequently. V3 can automatically repair a cached flow when it fails. For new V4 integrations, use [saved scripts](/cloud/agent/scripts); the V3 `@{{value}}` convention is specific to this tutorial’s API version.
## 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.
* [Scripts](/cloud/agent/scripts) — Save, reuse, and repair browser workflows.
# Claude Code
Source: https://docs.browser-use.com/cloud/tutorials/integrations/claude-code
Give Claude Code cloud browser automation with Browser Use.
[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 theme={null}
uv tool install browser-use
```
**2. Verify the installation**
```bash theme={null}
browser-use doctor
```
**3. Register the skill**
Register the Browser Use skill with the installed CLI:
```bash theme={null}
browser-use skill install
```
**4. Authenticate for cloud browsers**
Create an [API key](https://cloud.browser-use.com/settings?tab=api-keys\&new=1), then authenticate:
```bash theme={null}
browser-use auth login
```
**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).
# Claude Managed Agents
Source: 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.
[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 theme={null}
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 theme={null}
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 theme={null}
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.
# Hermes Agent
Source: https://docs.browser-use.com/cloud/tutorials/integrations/hermes-agent
Give Hermes Agent cloud browser automation with Browser Use.
[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**
Create an API key from [Settings → API Keys](https://cloud.browser-use.com/settings?tab=api-keys\&new=1).
Running Hermes without a human? Use [x402 pay-per-request](/cloud/guides/x402) instead; it needs no account or API key.
**2. Configure Hermes**
Run the setup wizard:
```bash theme={null}
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 theme={null}
BROWSER_USE_API_KEY=your_key_here
```
And set the provider in `~/.hermes/config.yaml`:
```yaml theme={null}
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 theme={null}
uv tool install browser-use
```
**2. Verify the installation**
```bash theme={null}
browser-use doctor
```
**3. Register the skill**
Register the Browser Use skill with the installed CLI:
```bash theme={null}
browser-use skill install
```
Or ask Hermes directly in chat to install it.
**4. Authenticate for cloud browsers**
Authenticate with your API key:
```bash theme={null}
browser-use auth login
```
**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).
# n8n
Source: https://docs.browser-use.com/cloud/tutorials/integrations/n8n
Use Browser Use as an HTTP node in n8n workflows.
Browser Use works with [n8n](https://n8n.io) as a standard HTTP integration — no custom nodes needed.
## 1. Create a credential
In n8n, go to **Credentials → Add Credential → Header Auth** and set:
| Field | Value |
| ----- | ------------------------------------------ |
| Name | `X-Browser-Use-API-Key` |
| Value | `YOUR_API_KEY` (without a `Bearer` prefix) |
Get your API key at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys\&new=1).
## 2. Start a session
Add an **HTTP Request** node:
| Setting | Value |
| -------------- | --------------------------------------------- |
| Method | `POST` |
| URL | `https://api.browser-use.com/api/v3/sessions` |
| Authentication | Header Auth (from step 1) |
| Body Type | JSON |
Body:
```json theme={null}
{
"task": "Find the top 3 trending repos on GitHub today"
}
```
The response includes an `id` you'll use to poll for results. Save it in your workflow so later nodes can still reference this session.
## 3. Poll for completion
Add a second **HTTP Request** node in a loop:
| Setting | Value |
| -------------- | ------------------------------------------------------------ |
| Method | `GET` |
| URL | `https://api.browser-use.com/api/v3/sessions/{{ $json.id }}` |
| Authentication | Header Auth (from step 1) |
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.
The final response contains `output` with the agent's result.
## Event-driven alternative
Instead of polling, use [Webhooks](/cloud/guides/webhooks) to receive status changes. 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 V3 `session.status.update` events. Match `payload.session_id` to the `id` saved from the create response, then check `payload.status` for completion. The `agent.task.status_update` event belongs to V2 tasks.
```json theme={null}
{
"type": "session.status.update",
"timestamp": "2026-09-09T12:00:00Z",
"payload": {
"session_id": "the-id-from-your-create-response",
"status": "idle",
"output": "The agent's result"
}
}
```
Validate the signature before accepting a webhook. See the [verification example](/cloud/guides/webhooks#signature-verification); use a verified receiver in front of n8n if your workflow cannot perform that verification itself.
This pattern works with any workflow tool that supports HTTP requests — Make, Zapier, Pipedream, or custom orchestrators.
# OpenClaw
Source: https://docs.browser-use.com/cloud/tutorials/integrations/openclaw
Give OpenClaw agents browser automation with Browser Use — via CDP or the CLI skill.
[OpenClaw](https://openclaw.ai) is a self-hosted gateway that connects chat apps like WhatsApp, Telegram, and Discord to AI coding agents. Add Browser Use and those agents get full browser automation — anti-detect profiles, CAPTCHA solving, residential proxies in 195+ countries, and stealth browsing out of the box.
Two ways to set it up: connect a Browser Use cloud browser to OpenClaw's native browser tool via CDP, or install the Browser Use CLI as a skill.
## Option 1: Cloud Browser via CDP
OpenClaw has a built-in browser tool with its own CLI commands (`openclaw browser`). By default, it controls a local Chromium instance. You can point it at a Browser Use cloud browser instead by configuring a remote CDP profile.
Browser Use exposes a WebSocket CDP URL. OpenClaw connects to it like any remote browser — no SDK or extra dependencies needed.
### 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).
**2. Add a Browser Use profile**
Open `~/.openclaw/openclaw.json` and add a `browser-use` profile:
```json5 theme={null}
{
browser: {
enabled: true,
defaultProfile: "browser-use",
remoteCdpTimeoutMs: 3000,
remoteCdpHandshakeTimeoutMs: 5000,
profiles: {
"browser-use": {
cdpUrl: "wss://connect.browser-use.com?apiKey=&proxyCountryCode=us",
color: "#ff750e",
},
},
},
}
```
Replace `` with your actual key. All Browser Use session parameters can be passed as query params in the `cdpUrl`:
* `timeout` — session duration in minutes (max 240)
* `profileId` — load a saved browser profile with persistent cookies and localStorage
* `proxyCountryCode` — route traffic through a specific country (e.g. `us`, `de`, `jp`)
**3. Use it**
OpenClaw's browser commands now run against a Browser Use cloud browser:
```bash theme={null}
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 theme={null}
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 theme={null}
uv tool install browser-use
```
**2. Verify the installation**
```bash theme={null}
browser-use doctor
```
**3. Set up the agent**
Paste this setup prompt into your OpenClaw agent:
```text theme={null}
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).
# Prompt for Vibecoders
Source: https://docs.browser-use.com/cloud/vibecoding
Current Cloud documentation, API schemas, concurrency, and billing guidance for AI coding agents.
Give your coding agent (Cursor, Claude Code, Windsurf, or another tool) the current documentation index:
```text theme={null}
https://docs.browser-use.com/llms.txt
```
Ask it to use the **Cloud** pages and **API V4** for a new hosted agent integration. The open-source library has a separate API.
The index links to Markdown pages and API specifications. For a single bundle of documentation, use [llms-full.txt](https://docs.browser-use.com/.well-known/llms-full.txt). These managed exports include guidance on [concurrency](/cloud/guides/concurrency) and [billing](/cloud/guides/billing).
Full bundles can be cached for up to 24 hours. For the freshest guidance, have your agent follow the index to the linked `.md` pages. Use these managed exports instead of the older `/cloud/llms*.txt` or `/open-source/llms*.txt` URLs.
If you need a full snapshot immediately after a docs update, fetch the bundle
with a fresh query value:
```bash theme={null}
curl --fail --location \
"https://docs.browser-use.com/llms-full.txt?updated=$(date +%s)" \
--output browser-use-docs.txt
```
This requests a fresh cache entry; it does not update a copy your coding agent
already downloaded. Replace that copy or ask the agent to fetch it again.
# Which Browser Use do I need?
Source: https://docs.browser-use.com/cloud/which-product
Run Browser Use on your machine, run the full stack in our cloud, or bring your own agent and use only the hosted browser.
Pick where your agent and browser should run.
## On your computer
Claude Code, Codex, OpenCode, Hermes Agent, or another local agent can run the [Browser Use CLI](https://docs.browser-use.com/open-source/browser-use-cli) on your computer. Local computer-use CLIs such as Playwright CLI and Agent Browser fit the same shape. They can control real Chrome, with its tabs and logins, or use the single hosted browser shown in our cloud.
Start with real Chrome. Connect to our cloud when a site blocks you or you need many browsers at once. Run `browser-use auth login` once before the first cloud connection.
[Profile Sync](/cloud/guides/profile-sync) copies selected cookies from real Chrome into a cloud profile. Run it explicitly for the accounts you want to use in the cloud; the dashed line does not mean automatic synchronization.
## In our cloud
[Cloud v4](/cloud/quickstart) runs OpenCode, Browser Use CLI and Cloud Browser. You send a prompt to our API and get the result back, plus a live preview and any files it produced. Nothing runs on your computer.
Use the full hosted stack for scheduled work, tasks your users trigger, and work that should keep running without your computer.
## Only the browser
Use the [Browser API](/cloud/browser/quickstart) when you only need the hosted browser. Browser Use CLI or a custom computer-use CLI can connect to the same Cloud Browser from your computer or your own cloud; Browser API does not add a hosted agent.
One API key from [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=api-keys\&new=1) works for all three. Credits and profiles live in the same project.
# Browser Use CLI
Source: https://docs.browser-use.com/open-source/browser-use-cli
Direct browser control for coding agents.
The Browser Use CLI (`browser-use`) gives coding agents a direct browser-control surface backed by [Browser Harness](https://github.com/browser-use/browser-harness):
* **Direct browser control** — agents run Python to do actions in the browser.
* **Three browser modes** — you can use with local Chrome or Chromium with your existing tabs, cookies, extensions, and logins; Browser Use cloud browsers; or any browser reachable through a CDP endpoint.
* **Agent-ready setup** — install the skill into Claude Code, Codex, and other coding agents so they know when and how to call the CLI.
Try out Browser Use CLI with an agent in [Browser Use Cloud](https://cloud.browser-use.com?utm_source=docs\&utm_medium=browser-use-cli\&utm_campaign=v4), or install the skill to try it yourself locally.
## Install the CLI
```bash theme={null}
uv tool install browser-use
browser-use --help
```
For one-off runs without a permanent tool install, use `uvx browser-use`.
## Set Up Your Agent
Paste this setup prompt into Claude Code, Codex, or another coding agent:
```text theme={null}
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 skill is discoverable from [skills.sh](https://skills.sh/browser-use/browser-use/browser-use).
## Use Python Directly
Pass Python to the CLI. Use `uvx browser-use` for one-off runs, or `browser-use` if you installed it:
```bash theme={null}
uvx browser-use <<'PY'
new_tab("https://example.com")
print(page_info())
PY
```
The `<<'PY'` form is for Unix-compatible shells such as bash, zsh, Git Bash, or WSL. In PowerShell, pipe a here-string instead:
```powershell theme={null}
@'
new_tab("https://example.com")
print(page_info())
'@ | uvx browser-use
```
## Local Browser Setup
The default local flow attaches to your running Chrome or Chromium through CDP. This preserves the browser state the user already has locally: open tabs, cookies, extensions, and logged-in sessions. It is the right default for desktop work where the user can approve Chrome's remote-debugging prompt.
You can also point the CLI at any existing CDP browser by setting `BU_CDP_URL` or `BU_CDP_WS` before running `browser-use`. Use that for managed Chrome instances, Playwright-launched browsers, or infrastructure that already exposes a DevTools endpoint.
If the CLI cannot connect, run:
```bash theme={null}
browser-use --doctor
```
If Chrome asks whether to allow remote debugging, approve it and rerun the command.
## Cloud Browsers
Use Browser Use cloud browsers when the agent runs on a headless machine, needs an isolated browser, needs parallel browser sessions, or needs Browser Use Cloud features such as persistent cloud profiles, proxy routing, CAPTCHA handling, and live browser viewing.
The Browser Harness-backed CLI keeps cloud sessions explicit. You authenticate once, start a named cloud browser, then use that name for later commands:
```bash theme={null}
browser-use auth login
```
```bash theme={null}
browser-use <<'PY'
start_remote_daemon("work")
PY
BU_NAME=work browser-use <<'PY'
new_tab("https://example.com")
print(page_info())
PY
```
Use short, task-specific names when you have multiple agents or sub-agents running in parallel. Each name maps to its own remote browser daemon.
Remote browsers bill until they stop or time out. When the task is done, ask whether to close the browser; if yes, run:
```bash theme={null}
BU_NAME=work browser-use <<'PY'
stop_remote_daemon("work")
PY
```
## Useful Commands
```bash theme={null}
browser-use --help
browser-use --doctor
browser-use auth login
browser-use auth status
browser-use skill show
browser-use telemetry status
```
# Browser Use Terminal
Source: https://docs.browser-use.com/open-source/browser-use-terminal
Use the terminal app for browser agents, or use the same runtime from Python.
Browser Use Terminal is a Codex-style assistant for the browser.
Coding assistants like Claude Code and Codex work well because they give the model autonomy and a real environment to operate in. They provide tools, files, shell commands, history, resumable sessions, and a terminal UI where you can watch and steer the work.
Browser Use Terminal brings that pattern to browser automation. It gives an AI agent its own browser environment: it can control Chrome, see screenshots, inspect pages, run page JavaScript, save files, use browser profiles, handle secrets, and keep a history of what happened.
Learn more on the [Browser Use Terminal site](https://browser-use.com/terminal?utm_source=docs) or view the [Terminal GitHub repo](https://github.com/browser-use/terminal?utm_source=docs).
In practice, this means you can ask for browser work from your terminal:
```text theme={null}
Find the cancellation policy for my current hotel reservation.
```
```text theme={null}
Give this employee admin permission in Azure.
```
```text theme={null}
Research the top Hacker News stories and summarize the points.
```
You can use Browser Use Terminal in four ways:
| Surface | What it is for |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **TUI** | The interactive terminal app you launch with `browser`. Best for normal, hands-on use. |
| **CLI** | Scriptable commands such as `browser-use-terminal run-openai`. Best for automation and one-off tasks. |
| **Python** | `browser_use.beta.Agent`, backed by the same Rust runtime. Best when you want browser-use code with terminal runtime behavior. |
| **Coding assistants** | A skill plus the `browser-use-terminal browser` commands let Claude Code, Codex, OpenCode, and other agents drive the browser. See [Use From Coding Assistants](#use-from-coding-assistants). |
Browser Use Terminal is separate from the lower-level `browser-use` CLI. Use
`browser` for the Terminal TUI, `browser-use-terminal` for Terminal task
commands, and `browser-use` for direct browser-control commands.
## Terms
* **TUI**: terminal user interface. This is the interactive app you open with `browser`.
* **CLI**: command-line interface. These are scriptable commands such as `browser-use-terminal run-openai`.
* **Headless browser**: a browser that runs without a visible window.
* **CDP**: Chrome DevTools Protocol. This lets Browser Use connect to an existing Chrome instance.
* **MCP**: Model Context Protocol. This lets the agent use tools from external MCP servers.
* **TOTP**: time-based one-time password. This is the six-digit code generated by authenticator apps for 2FA.
## Install
Install Browser Use Terminal:
```bash theme={null}
curl -fsSL https://browser-use.com/terminal/install.sh | sh
```
Restart your shell or source your shell profile if the installer updates your `PATH`.
Verify the two terminal executables:
```bash theme={null}
which browser
which browser-use-terminal
browser-use-terminal --help
```
Open the TUI:
```bash theme={null}
browser
```
Set up Python usage in the same project where you will write your agent code:
```bash theme={null}
uv venv --python 3.12
source .venv/bin/activate
uv pip install browser-use
```
## TUI Commands
In the TUI, type `/` to open the command palette. These are the main commands:
| Command | What it does |
| ------------------- | ----------------------------------------------------------------- |
| `/task` | Start a new task. |
| `/model` | Choose the model and provider. |
| `/auth` | Sign in to a provider. |
| `/browser` | Change the browser backend. |
| `/profile` | Choose the default Chrome profile. |
| `/history` | Browse previous tasks. |
| `/context` | Inspect context window attribution. |
| `/secrets` | Save passwords and 2FA setup keys for website logins. |
| `/import-passwords` | Import saved logins from 1Password. |
| `/domains` | Allow or block which sites the agent can visit. |
| `/sync-cookies` | Sync local cookies. |
| `/email` | Give the agent a disposable inbox for sign-ups, links, and codes. |
| `/goal` | Set or view the goal for a long-running task. |
| `/feedback` | Report a bug or share feedback. |
| `/update` | Install the latest release. |
| `/reload` | Restart the UI in this terminal. |
| `/exit` | Quit Browser Use Terminal. |
## Run Browser Tasks
### TUI
The TUI is the main Browser Use Terminal experience:
```bash theme={null}
browser
```
Type a browser task. You can watch the agent work, interrupt it, steer it, and continue the same session later.
```text theme={null}
Open my company's dashboard and summarize failed jobs from today.
```
```text theme={null}
Log in to the vendor portal and download the latest invoice.
```
### CLI
Use one-shot commands for scripts, automation, and quick tasks:
```bash theme={null}
browser-use-terminal --help
```
```bash theme={null}
browser-use-terminal run-openai --model gpt-5.5 \
"Go to https://news.ycombinator.com and return the top stories with points"
```
Other provider commands:
```bash theme={null}
browser-use-terminal run-anthropic --model claude-sonnet-4-6 "Research Browser Use"
browser-use-terminal run-openrouter --model openai/gpt-5.5 "Research Browser Use"
browser-use-terminal run-deepseek --model deepseek-v4-pro "Research Browser Use"
```
### Python
Use `browser_use.beta.Agent` to run Python code on top of the Rust terminal runtime:
```bash theme={null}
uv pip install browser-use
```
```python theme={null}
import asyncio
from browser_use.beta import Agent, BrowserProfile, ChatOpenAI
async def main():
agent = Agent(
task="Go to https://news.ycombinator.com and return the top stories with points.",
llm=ChatOpenAI(model="gpt-5.5"),
browser_profile=BrowserProfile(
headless=True,
window_size={"width": 1440, "height": 900},
allowed_domains=["news.ycombinator.com"],
),
)
history = await agent.run(max_steps=12)
print(history.final_result())
asyncio.run(main())
```
## Use From Coding Assistants
Browser Use Terminal plugs into any coding assistant that can run shell commands. A skill teaches the assistant the CLI, and the CLI gives it the full browser runtime: page interaction through Python helpers, screenshots saved as files, profiles, and more.
The fastest setup is to paste this URL into your assistant and let it bootstrap everything (install, skill registration, browser setup, verification):
```text theme={null}
https://browser-use.com/skill
```
To set it up manually instead:
```bash theme={null}
curl -fsSL https://browser-use.com/terminal/install.sh | sh
browser-use-terminal skill install
```
`skill install` writes the skill into every detected assistant home: `~/.claude/skills/` (Claude Code, also read by OpenCode), `~/.codex/skills/` (Codex), `~/.config/opencode/skills/`, and `~/.agents/skills/`. For other assistants, persist the output of `browser-use-terminal skill show` wherever that assistant reads instructions.
The assistant then drives the browser with commands:
```bash theme={null}
browser-use-terminal browser exec <<'PY'
new_tab("https://example.com")
wait_for_load()
print(page_info()["title"])
print(capture_screenshot())
PY
```
How it works:
* The browser auto-connects per the remembered preference (`browser-use-terminal browser preference use local|cloud|managed-headless`) and persists across invocations; Python variables do not.
* Screenshots are saved as files and the absolute path is printed, so assistants view them with their native file tools: Claude Code `Read`, Codex `view_image`, OpenCode `read`, Gemini CLI `read_file`.
* `--session ` gives parallel workstreams isolated artifact dirs, event logs, and managed browsers.
* Stop persistent browsers with `browser-use-terminal browser recover stop-owned-browser` (managed) or `browser-use-terminal browser recover stop-owned-remote` (cloud).
* Everything is recorded in the same event log as the TUI: `browser-use-terminal events browser-cli-`.
## Configure Models
### TUI
Use `/auth` to sign in and configure credentials, then `/model` to choose the model:
```text theme={null}
/auth
/model
```
This is the recommended setup path for normal interactive use.
### CLI
For one-shot commands and CI, set the provider key for the provider command you are using:
```bash theme={null}
export OPENAI_API_KEY=...
export ANTHROPIC_API_KEY=...
export OPENROUTER_API_KEY=...
export DEEPSEEK_API_KEY=...
export BROWSER_USE_API_KEY=...
```
Then choose a provider command and model:
```bash theme={null}
browser-use-terminal run-openai --model gpt-5.5 "Research Browser Use"
browser-use-terminal run-anthropic --model claude-sonnet-4-6 "Research Browser Use"
browser-use-terminal run-openrouter --model openai/gpt-5.5 "Research Browser Use"
browser-use-terminal run-deepseek --model deepseek-v4-pro "Research Browser Use"
```
### Python
Pass a supported browser-use model class to `Agent`:
```python theme={null}
from browser_use.beta import Agent, ChatAnthropic
agent = Agent(
task="Find the latest Browser Use docs update",
llm=ChatAnthropic(model="claude-sonnet-4-6"),
)
```
Supported Python model classes:
| Python model class | Example |
| ------------------ | ------------------------------------------ |
| `ChatOpenAI` | `ChatOpenAI(model="gpt-5.5")` |
| `ChatAnthropic` | `ChatAnthropic(model="claude-sonnet-4-6")` |
| `ChatLiteLLM` | `ChatLiteLLM(model="openai/gpt-5.5")` |
| `ChatDeepSeek` | `ChatDeepSeek(model="deepseek-v4-pro")` |
| `ChatOpenRouter` | `ChatOpenRouter(model="openai/gpt-5.5")` |
## Choose A Browser
### TUI
Use `/browser` to choose how the agent should run the browser:
```text theme={null}
/browser
```
Common choices are your local Chrome, a headless browser, or Browser Use Cloud.
Browser Use Cloud is the easiest option to get started: it gives you a free, one-click browser setup without managing a local Chrome profile.
Use `/profile` to choose the default Chrome profile:
```text theme={null}
/profile
```
### CLI
Use terminal config or one-off config overrides for browser settings:
```bash theme={null}
browser-use-terminal \
--config 'browser.headless=true' \
--config 'browser.window_size={width=1440,height=900}' \
run-openai "Open Hacker News"
```
### Python
Managed local browser:
```python theme={null}
from browser_use.beta import Agent, BrowserProfile, ChatOpenAI
agent = Agent(
task="Open Hacker News",
llm=ChatOpenAI(model="gpt-5.5"),
browser_profile=BrowserProfile(
headless=True,
user_data_dir="./browser-profile",
window_size={"width": 1280, "height": 900},
),
)
```
Headed browser:
```python theme={null}
agent = Agent(
task="Open Hacker News",
llm=ChatOpenAI(model="gpt-5.5"),
browser_profile=BrowserProfile(headless=False),
)
```
Browser Use Cloud:
Browser Use Cloud is the simplest remote browser option for Python too. Set `BROWSER_USE_API_KEY`, pass `use_cloud=True`, and the terminal runtime handles the cloud browser session.
```python theme={null}
from browser_use.beta import Agent, BrowserSession, ChatOpenAI
agent = Agent(
task="Open Hacker News",
llm=ChatOpenAI(model="gpt-5.5"),
browser_session=BrowserSession(
use_cloud=True,
cloud_profile_id="profile-id",
cloud_proxy_country_code="US",
keep_alive=False,
),
)
```
Existing browser over CDP:
```python theme={null}
from browser_use.beta import Agent, BrowserProfile, ChatOpenAI
agent = Agent(
task="Open Hacker News",
llm=ChatOpenAI(model="gpt-5.5"),
browser_profile=BrowserProfile(
cdp_url="http://127.0.0.1:9333",
user_agent="BrowserUseRemoteCDP/1.0",
),
)
```
## Use Secrets And Login
Secrets let the agent fill credentials without exposing raw values to the model.
### TUI
Use `/auth` for Browser Use account and model-provider authentication:
```text theme={null}
/auth
```
Use `/secrets` to save website login secrets and 2FA setup keys:
```text theme={null}
/secrets
```
Inside `/secrets`, press `Ctrl-O` to import logins from 1Password. You can also run:
```text theme={null}
/import-passwords
```
Name a TOTP secret `otp` and paste the authenticator setup key, not the current six-digit code.
### CLI
Store a password:
```bash theme={null}
printf '%s' "$PASSWORD" | browser-use-terminal secrets set \
--domain example.com \
--name password \
--stdin
```
Store a TOTP seed for 2FA:
```bash theme={null}
printf '%s' "$TOTP_SEED" | browser-use-terminal secrets set \
--domain example.com \
--name otp \
--totp \
--stdin
```
Import saved logins from 1Password:
```bash theme={null}
browser-use-terminal secrets import
browser-use-terminal secrets list
```
The import command uses the `op` CLI. `secrets list` prints metadata only, never secret values.
### Python
Python runs use the same terminal runtime and terminal state. Configure website secrets with the terminal commands above, then run the Python agent against the matching domain.
```python theme={null}
from browser_use.beta import Agent, BrowserProfile, ChatOpenAI
agent = Agent(
task="Log in to example.com and summarize my dashboard.",
llm=ChatOpenAI(model="gpt-5.5"),
browser_profile=BrowserProfile(allowed_domains=["example.com"]),
)
```
## Restrict Domains
Domain policy controls where the browser can navigate.
### TUI
Use `/domains` to allow or block sites from the TUI:
```text theme={null}
/domains
```
### CLI
```bash theme={null}
browser-use-terminal domains allow news.ycombinator.com
browser-use-terminal domains deny '*.tracking.example'
browser-use-terminal domains list
browser-use-terminal domains clear
```
### Python
Pass domain policy through `BrowserProfile`:
```python theme={null}
from browser_use.beta import BrowserProfile
profile = BrowserProfile(
allowed_domains=["news.ycombinator.com"],
prohibited_domains=["*.tracking.example"],
)
```
## Use MCP Servers
MCP servers add external tools to the agent.
### TUI
Add MCP servers to terminal config, then run the TUI normally:
```bash theme={null}
browser
```
### CLI
Create an MCP config:
```toml theme={null}
[mcp_servers.local]
transport = "stdio"
command = "python"
args = ["./server.py"]
```
Pass it when you run a task:
```bash theme={null}
browser-use-terminal --mcp-config ./mcp.toml run-openai \
"Use the browser and MCP tools to complete this task"
```
### Python
Python terminal-backed runs can use terminal MCP configuration through the same terminal runtime. Configure MCP servers in terminal config before starting the Python run.
## Use Profiles And Config
Profiles let you keep separate terminal settings for different accounts, browsers, models, or workflows.
### TUI
Use `/profile` to choose the default Chrome profile:
```text theme={null}
/profile
```
Open the terminal with a named terminal config profile:
```bash theme={null}
browser --profile work
```
### CLI
The terminal reads config from:
```text theme={null}
$BROWSER_USE_TERMINAL_HOME/config.toml
```
If `BROWSER_USE_TERMINAL_HOME` is not set, it uses:
```text theme={null}
~/.browser-use-terminal
```
Use a named profile to layer `$BROWSER_USE_TERMINAL_HOME/.config.toml` on top of the base config:
```bash theme={null}
browser-use-terminal --profile work run-openai "Research this pricing page"
```
Use `--state-dir` for an isolated run:
```bash theme={null}
browser-use-terminal --state-dir /tmp/browser-use-terminal run-openai \
"Open Hacker News and summarize the front page"
```
### Python
Point Python at a terminal state directory:
```bash theme={null}
export BROWSER_USE_TERMINAL_HOME=/path/to/state
```
Use a specific terminal binary:
```bash theme={null}
export BROWSER_USE_TERMINAL_BINARY=/path/to/browser-use-terminal
```
## Troubleshooting Setup
### `browser` Or `browser-use-terminal` Not Found
Restart your terminal, then check whether the install location is on your `PATH`:
```bash theme={null}
which browser
which browser-use-terminal
echo "$PATH"
```
If the commands are still missing, rerun the installer and copy any `PATH` instructions it prints:
```bash theme={null}
curl -fsSL https://browser-use.com/terminal/install.sh | sh
```
### Which Command Should I Use?
| Goal | Command |
| ----------------------------------------- | --------------------------------------- |
| Open the interactive Terminal app | `browser` |
| Run a one-shot Terminal agent task | `browser-use-terminal run-openai "..."` |
| Use Terminal runtime from Python | `browser_use.beta.Agent` |
| Drive the browser from a coding assistant | `browser-use-terminal browser exec` |
| Register the skill with coding assistants | `browser-use-terminal skill install` |
| Directly control a browser with Python | `browser-use <<'PY' ... PY` |
### Python Cannot Find `browser_use.beta`
Install or upgrade the Python package in the same environment you are running:
```bash theme={null}
uv pip install --upgrade browser-use
python -c "from browser_use.beta import Agent; print('ok')"
```
## Forwarded Python Settings
When you use `browser_use.beta.Agent`, these Python settings are forwarded to the Rust terminal SDK:
| Python setting | Terminal SDK field |
| ------------------------------------------------ | ---------------------------- |
| `llm.provider`, inferred from the chat model | `llm.provider` |
| `llm.model` | `llm.model` |
| `llm_timeout` | `llm.timeout` |
| `headless` | `browser.headless` |
| `keep_alive` | `browser.keep_alive` |
| `cloud_profile_id`, `profile_id` | `browser.profile_id` |
| `cloud_proxy_country_code`, `proxy_country_code` | `browser.proxy_country_code` |
| `cdp_url` | `browser.cdp_url` |
| `headers` / CDP headers | `browser.cdp_headers` |
| `user_agent` | `browser.user_agent` |
| `viewport` | `browser.viewport` |
| `window_size` | `browser.window_size` |
| `storage_state` | `browser.storage_state` |
| `downloads_path` | `browser.downloads_path` |
| `allowed_domains` | `browser.allowed_domains` |
| `prohibited_domains` | `browser.blocked_domains` |
| `state_dir` | `browser.state_dir` |
| `no_viewport` | `browser.no_viewport` |
| `accept_downloads` | `browser.accept_downloads` |
| `output_model_schema` | `output_schema` |
| `calculate_cost` | `calculate_cost` |
| `max_actions_per_step` | `max_actions_per_step` |
The same Python `Agent` instance can continue a terminal-backed session across follow-up runs.
### Cloud Browser Setup
Set `BROWSER_USE_API_KEY` and pass the cloud profile or proxy settings through `BrowserSession`.
### 1Password Import Setup
Install and sign in to the 1Password CLI:
```bash theme={null}
op account list
```
Then import:
```bash theme={null}
browser-use-terminal secrets import
```
### TOTP Setup
Store the base32 TOTP seed with `--totp`, not the current six-digit code:
```bash theme={null}
printf '%s' "$BASE32_TOTP_SEED" | browser-use-terminal secrets set \
--domain example.com \
--name otp \
--totp \
--stdin
```
# All Parameters
Source: https://docs.browser-use.com/open-source/customize/agent/all-parameters
Complete reference for all agent configuration options — LLM settings, prompts, tools, output format, and callbacks.
## Available Parameters
### Core Settings
* `tools`: Registry of tools the agent can call. Example
* `skills` (or `skill_ids`): List of skill IDs to load (e.g., `['skill-uuid']` or `['*']` for all). Requires `BROWSER_USE_API_KEY`. Docs
* `browser`: Browser object where you can specify the browser settings.
* `output_model_schema`: Pydantic model class for structured output validation. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/custom_output.py)
### Vision & Processing
* `use_vision` (default: `True`): Vision mode - `"auto"` includes screenshot tool but only uses vision when requested, `True` always includes screenshots, `False` never includes screenshots and excludes screenshot tool
* `vision_detail_level` (default: `'auto'`): Screenshot detail level - `'low'`, `'high'`, or `'auto'`
* `page_extraction_llm`: Separate LLM model for page content extraction. You can choose a small & fast model because it only needs to extract text from the page (default: same as `llm`)
### Fallback & Resilience
* `fallback_llm`: Backup LLM to use when the primary LLM fails. The primary LLM will first exhaust its own retry logic (typically 5 attempts with exponential backoff), and only then switch to the fallback. Triggers on rate limits (429), authentication errors (401), payment/credit errors (402), or server errors (500, 502, 503, 504). Once switched, the fallback is used for the rest of the run. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/fallback_model.py)
### Actions & Behavior
* `initial_actions`: List of actions to run before the main task without LLM. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/initial_actions.py)
* `max_actions_per_step` (default: `5`): Maximum actions per step, e.g. for form filling the agent can output 4 fields at once. We execute the actions until the page changes.
* `max_failures` (default: `5`): Maximum retries for steps with errors
* `final_response_after_failure` (default: `True`): If True, attempt to force one final model call with intermediate output after max\_failures is reached
* `use_thinking` (default: `True`): Controls whether the agent uses its internal "thinking" field for explicit reasoning steps.
* `flash_mode` (default: `False`): Fast mode that skips evaluation, next goal and thinking and only uses memory. If `flash_mode` is enabled, it overrides `use_thinking` and disables the thinking process entirely. [Example](https://github.com/browser-use/browser-use/blob/main/examples/getting_started/05_fast_agent.py)
### System Messages
* `override_system_message`: Completely replace the default system prompt.
* `extend_system_message`: Add additional instructions to the default system prompt. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/custom_system_prompt.py)
### File & Data Management
* `save_conversation_path`: Path to save complete conversation history
* `save_conversation_path_encoding` (default: `'utf-8'`): Encoding for saved conversations
* `available_file_paths`: List of file paths the agent can access
* `sensitive_data`: Dictionary of sensitive data to handle carefully. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/sensitive_data.py)
### Visual Output
* `generate_gif` (default: `False`): Generate GIF of agent actions. Set to `True` or string path
* `include_attributes`: List of HTML attributes to include in page analysis
### Performance & Limits
* `max_history_items`: Maximum number of last steps to keep in the LLM memory. If `None`, we keep all steps.
* `llm_timeout` (default: `None`): Timeout in seconds for LLM calls. When omitted, the agent selects a model-dependent timeout of 30, 75, or 90 seconds
* `step_timeout` (default: `180`): Timeout in seconds for each step
* `directly_open_url` (default: `True`): If we detect a url in the task, we directly open it.
### Advanced Options
* `calculate_cost` (default: `False`): Calculate and track API costs
* `display_files_in_done_text` (default: `True`): Show file information in completion messages
### Backwards Compatibility
* `controller`: Alias for `tools` for backwards compatibility.
* `browser_session`: Alias for `browser` for backwards compatibility.
***
## Environment Variables
These environment variables can be used to tune agent and browser behavior without code changes. They are particularly useful for debugging, slow networks, or deployment-level tuning.
### Agent Timeouts
| Variable | Default | Description |
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `TIMEOUT_AgentEventBusStop` | `3.0` | Timeout in seconds for the agent's event bus to finish processing pending events during shutdown. |
### Browser Action Timeouts
| Variable | Default | Description |
| ----------------------------------- | ------- | ------------------------------------------------- |
| `TIMEOUT_NavigateToUrlEvent` | `30.0` | Timeout for page navigation |
| `TIMEOUT_ClickElementEvent` | `15.0` | Timeout for clicking elements |
| `TIMEOUT_ClickCoordinateEvent` | `15.0` | Timeout for clicking at coordinates |
| `TIMEOUT_TypeTextEvent` | `60.0` | Timeout for typing text (longer for large inputs) |
| `TIMEOUT_ScrollEvent` | `8.0` | Timeout for scrolling |
| `TIMEOUT_ScrollToTextEvent` | `15.0` | Timeout for scrolling to find text |
| `TIMEOUT_SendKeysEvent` | `60.0` | Timeout for sending keyboard shortcuts |
| `TIMEOUT_UploadFileEvent` | `30.0` | Timeout for file uploads |
| `TIMEOUT_GetDropdownOptionsEvent` | `15.0` | Timeout for fetching dropdown options |
| `TIMEOUT_SelectDropdownOptionEvent` | `8.0` | Timeout for selecting dropdown option |
| `TIMEOUT_GoBackEvent` | `15.0` | Timeout for browser back navigation |
| `TIMEOUT_GoForwardEvent` | `15.0` | Timeout for browser forward navigation |
| `TIMEOUT_RefreshEvent` | `15.0` | Timeout for page refresh |
| `TIMEOUT_WaitEvent` | `60.0` | Timeout for explicit wait actions |
| `TIMEOUT_ScreenshotEvent` | `15.0` | Timeout for taking screenshots |
| `TIMEOUT_BrowserStateRequestEvent` | `30.0` | Timeout for fetching browser state/DOM |
### Browser Lifecycle Timeouts
| Variable | Default | Description |
| ------------------------------- | ------- | ---------------------------------------- |
| `TIMEOUT_BrowserStartEvent` | `30.0` | Timeout for starting browser session |
| `TIMEOUT_BrowserStopEvent` | `45.0` | Timeout for stopping browser session |
| `TIMEOUT_BrowserLaunchEvent` | `30.0` | Timeout for launching browser process |
| `TIMEOUT_BrowserKillEvent` | `30.0` | Timeout for killing browser process |
| `TIMEOUT_BrowserConnectedEvent` | `30.0` | Timeout for CDP connection |
| `TIMEOUT_BrowserStoppedEvent` | `30.0` | Timeout for browser stopped confirmation |
| `TIMEOUT_BrowserErrorEvent` | `30.0` | Timeout for browser error events |
### Tab Management Timeouts
| Variable | Default | Description |
| -------------------------------- | ------- | ------------------------------- |
| `TIMEOUT_SwitchTabEvent` | `10.0` | Timeout for switching tabs |
| `TIMEOUT_CloseTabEvent` | `10.0` | Timeout for closing tabs |
| `TIMEOUT_TabCreatedEvent` | `30.0` | Timeout for tab creation events |
| `TIMEOUT_TabClosedEvent` | `10.0` | Timeout for tab closed events |
| `TIMEOUT_AgentFocusChangedEvent` | `10.0` | Timeout for focus change events |
| `TIMEOUT_TargetCrashedEvent` | `10.0` | Timeout for crash events |
### Navigation Event Timeouts
| Variable | Default | Description |
| --------------------------------- | ------- | -------------------------------------- |
| `TIMEOUT_NavigationStartedEvent` | `30.0` | Timeout for navigation started events |
| `TIMEOUT_NavigationCompleteEvent` | `30.0` | Timeout for navigation complete events |
### Storage & Download Timeouts
| Variable | Default | Description |
| --------------------------------- | ------- | --------------------------------------- |
| `TIMEOUT_SaveStorageStateEvent` | `45.0` | Timeout for saving cookies/localStorage |
| `TIMEOUT_StorageStateSavedEvent` | `30.0` | Timeout for storage save confirmation |
| `TIMEOUT_LoadStorageStateEvent` | `45.0` | Timeout for loading storage state |
| `TIMEOUT_StorageStateLoadedEvent` | `30.0` | Timeout for storage load confirmation |
| `TIMEOUT_FileDownloadedEvent` | `30.0` | Timeout for file download events |
### Example Usage
```bash theme={null}
# Increase timeouts for slow network or complex pages
export TIMEOUT_NavigateToUrlEvent=30.0
export TIMEOUT_TypeTextEvent=120.0
export TIMEOUT_BrowserStateRequestEvent=60.0
# Increase agent shutdown timeout
export TIMEOUT_AgentEventBusStop=10.0
```
# Configuration
Source: https://docs.browser-use.com/open-source/customize/agent/basics
Configure the agent — set your LLM, define tasks, add custom tools, and control behavior with Python.
```python theme={null}
from browser_use import Agent, ChatBrowserUse
agent = Agent(
task="Search for latest news about AI",
llm=ChatBrowserUse(),
)
async def main():
history = await agent.run(max_steps=100)
```
* `task`: The task you want to automate.
* `llm`: Your favorite LLM. See Supported Models.
The agent is executed using the async `run()` method:
* `max_steps` (default: `100`): Maximum number of steps an agent can take.
Check out all customizable parameters here.
# Output Format
Source: https://docs.browser-use.com/open-source/customize/agent/output-format
Control agent output — structured results, step history, action logs, and extracted content from browser automation tasks.
## Agent History
The `run()` method returns an `AgentHistoryList` object with the complete execution history:
```python theme={null}
history = await agent.run()
# Access useful information
history.urls() # List of visited URLs
history.screenshot_paths() # List of screenshot paths
history.screenshots() # List of screenshots as base64 strings
history.action_names() # Names of executed actions
history.extracted_content() # List of extracted content from all actions
history.errors() # List of errors (with None for steps without errors)
history.model_actions() # All actions with their parameters
history.model_outputs() # All model outputs from history
history.last_action() # Last action in history
# Analysis methods
history.final_result() # Get the final extracted content (last step)
history.is_done() # Check whether the agent emitted a done action
history.is_successful() # Agent-reported success (None if not done)
history.has_errors() # Check if any errors occurred
history.model_thoughts() # Get the agent's reasoning process (AgentBrain objects)
history.action_results() # Get all ActionResult objects from history
history.action_history() # Get truncated action history with essential fields
history.number_of_steps() # Get the number of steps in the history
history.total_duration_seconds() # Get total duration of all steps in seconds
# Structured output (when using output_model_schema)
history.structured_output # Property that returns parsed structured output
```
See all helper methods in the [AgentHistoryList source code](https://github.com/browser-use/browser-use/blob/main/browser_use/agent/views.py#L301).
## Structured Output
For structured output, use the `output_model_schema` parameter with a Pydantic model. [Example](https://github.com/browser-use/browser-use/blob/main/examples/features/custom_output.py).
`is_done()` does not imply success. `is_successful()` reports the agent's own
assessment; verify important external outcomes, such as a submitted form or a
completed purchase, against the destination system.
# Prompting Guide
Source: https://docs.browser-use.com/open-source/customize/agent/prompting-guide
Write effective prompts for the agent. Tips for task descriptions, multi-step workflows, and getting reliable structured output.
Prompting can drastically improve performance and solve existing limitations of the library.
### 1. Be Specific vs Open-Ended
**✅ Specific (Recommended)**
```python theme={null}
task = """
1. Go to https://quotes.toscrape.com/
2. Use extract action with the query "first 3 quotes with their authors"
3. Save results to quotes.csv using write_file action
4. Do a google search for the first quote and find when it was written
"""
```
**❌ Open-Ended**
```python theme={null}
task = "Go to web and make money"
```
### 2. Name Actions Directly
When you know exactly what the agent should do, reference actions by name:
```python theme={null}
task = """
1. Use search action to find "Python tutorials"
2. Use click to open first result in a new tab
3. Use scroll action to scroll down 2 pages
4. Use extract to extract the names of the first 5 items
5. Wait for 2 seconds if the page is not loaded, refresh it and wait 10 sec
6. Use send_keys action with "Tab Tab ArrowDown Enter"
"""
```
See [Available Tools](/open-source/customize/tools/available) for the complete list of actions.
### 3. Handle interaction problems via keyboard navigation
Sometimes buttons cannot be clicked (you may have found a bug in the library - open an issue).
In many cases, you can work around this with keyboard navigation.
```python theme={null}
task = """
If the submit button cannot be clicked:
1. Use send_keys action with "Tab Tab Enter" to navigate and activate
2. Or use send_keys with "ArrowDown ArrowDown Enter" for form submission
"""
```
### 4. Custom Actions Integration
```python theme={null}
# When you have custom actions
@controller.action("Get 2FA code from authenticator app")
async def get_2fa_code():
# Your implementation
pass
task = """
Login with 2FA:
1. Enter username/password
2. When prompted for 2FA, use get_2fa_code action
3. NEVER try to extract 2FA codes from the page manually
4. ALWAYS use the get_2fa_code action for authentication codes
"""
```
### 5. Error Recovery
```python theme={null}
task = """
Robust data extraction:
1. Go to openai.com to find their CEO
2. If navigation fails due to anti-bot protection:
- Use google search to find the CEO
3. If page times out, use go_back and try alternative approach
"""
```
The key to effective prompting is being specific about actions.
# All Parameters
Source: https://docs.browser-use.com/open-source/customize/browser/all-parameters
Complete reference for all browser configuration options — launch args, proxy, viewport, user data, and CDP settings.
The `Browser` instance also provides all [Actor](/open-source/legacy/actor/all-parameters) methods for direct browser control (page management, element interactions, etc.).
## Core Settings
* `cdp_url`: CDP URL for connecting to existing browser instance (e.g., `"http://localhost:9222"`)
## Display & Appearance
* `headless` (default: `None`): Run browser without UI. Auto-detects based on display availability (`True`/`False`/`None`)
* `window_size`: Browser window size for headful mode. Use dict `{'width': 1920, 'height': 1080}` or `ViewportSize` object
* `window_position` (default: `{'width': 0, 'height': 0}`): Window position from top-left corner in pixels
* `viewport`: Content area size, same format as `window_size`. Use `{'width': 1280, 'height': 720}` or `ViewportSize` object
* `no_viewport` (default: `None`): Disable viewport emulation, content fits to window size
* `device_scale_factor`: Device scale factor (DPI). Set to `2.0` or `3.0` for high-resolution screenshots
## Browser Behavior
* `keep_alive` (default: `None`): Keep browser running after agent completes
* `allowed_domains`: Restrict navigation to specific domains. Domain pattern formats:
* `'example.com'` - Matches only `https://example.com/*`
* `'*.example.com'` - Matches `https://example.com/*` and any subdomain `https://*.example.com/*`
* `'http*://example.com'` - Matches both `http://` and `https://` protocols
* `'chrome-extension://*'` - Matches any Chrome extension URL
* **Security**: Wildcards in TLD (e.g., `example.*`) are **not allowed** for security
* Use list like `['*.google.com', 'https://example.com', 'chrome-extension://*']`
* **Performance**: Lists with 100+ domains are automatically optimized to sets for O(1) lookup. Pattern matching is disabled for optimized lists. Both `www.example.com` and `example.com` variants are checked automatically.
* `prohibited_domains`: Block navigation to specific domains. Uses same pattern formats as `allowed_domains`. When both `allowed_domains` and `prohibited_domains` are set, `allowed_domains` takes precedence. Examples:
* `['pornhub.com', '*.gambling-site.net']` - Block specific sites and all subdomains
* `['https://explicit-content.org']` - Block specific protocol/domain combination
* **Performance**: Lists with 100+ domains are automatically optimized to sets for O(1) lookup (same as `allowed_domains`)
* `enable_default_extensions` (default: `True`): Load automation extensions (uBlock Origin, cookie handlers, ClearURLs)
* `cross_origin_iframes` (default: `False`): Enable cross-origin iframe support (may cause complexity)
* `is_local` (default: `True`): Whether this is a local browser instance. Set to `False` for remote browsers. If we have a `executable_path` set, it will be automatically set to `True`. This can affect your download behavior.
## User Data & Profiles
* `user_data_dir` (default: auto-generated temp): Directory for browser profile data. Use `None` for incognito mode
* `profile_directory` (default: `'Default'`): Chrome profile subdirectory name (`'Profile 1'`, `'Work Profile'`, etc.)
* `storage_state`: Browser storage state (cookies, localStorage). Can be file path string or dict object
## Network & Security
* `proxy`: Proxy configuration using `ProxySettings(server='http://host:8080', bypass='localhost,127.0.0.1', username='user', password='pass')`
* `permissions` (default: `['clipboardReadWrite', 'notifications']`): Browser permissions to grant. Use list like `['camera', 'microphone', 'geolocation']`
* `headers`: Additional HTTP headers for connect requests (remote browsers only)
## Browser Launch
* `executable_path`: Path to browser executable for custom installations. Platform examples:
* macOS: `'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'`
* Windows: `'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'`
* Linux: `'/usr/bin/google-chrome'`
* `channel`: Browser channel (`'chromium'`, `'chrome'`, `'chrome-beta'`, `'msedge'`, etc.)
* `args`: Additional command-line arguments for the browser. Use list format: `['--disable-gpu', '--custom-flag=value', '--another-flag']`
* `env`: Environment variables for browser process. Use dict like `{'DISPLAY': ':0', 'LANG': 'en_US.UTF-8', 'CUSTOM_VAR': 'test'}`
* `chromium_sandbox` (default: `True` except in Docker): Enable Chromium sandboxing for security
* `devtools` (default: `False`): Open DevTools panel automatically (requires `headless=False`)
* `ignore_default_args`: List of default args to disable, or `True` to disable all. Use list like `['--enable-automation', '--disable-extensions']`
## Timing & Performance
* `minimum_wait_page_load_time` (default: `0.25`): Minimum time to wait before capturing page state in seconds
* `wait_for_network_idle_page_load_time` (default: `0.5`): Time to wait for network activity to cease in seconds
* `wait_between_actions` (default: `0.1`): Time to wait between agent actions in seconds
## AI Integration
* `highlight_elements` (default: `True`): Highlight interactive elements for AI vision
* `paint_order_filtering` (default: `True`): Enable paint order filtering to optimize DOM tree by removing elements hidden behind others. Slightly experimental
## Downloads & Files
* `accept_downloads` (default: `True`): Automatically accept all downloads
* `downloads_path`: Directory for downloaded files. Use string like `'./downloads'` or `Path` object
* `auto_download_pdfs` (default: `True`): Automatically download PDFs instead of viewing in browser
## Device Emulation
* `user_agent`: Custom user agent string. Example: `'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)'`
* `screen`: Screen size information, same format as `window_size`
## Recording & Debugging
Video recording requires additional optional dependencies. If these are not installed, no video will be saved and no error will be raised.
Install with:
```bash theme={null}
pip install "browser-use[video]"
```
or:
```bash theme={null}
pip install "imageio[ffmpeg]" numpy
```
* `record_video_dir`: Directory to save video recordings as `.mp4` files
* `record_video_size` (default: `ViewportSize`): The frame size (width, height) of the video recording.
* `record_video_framerate` (default: `30`): The framerate to use for the video recording.
* `record_har_path`: Path to save network trace files as `.har` format
* `traces_dir`: Directory to save complete trace files for debugging
* `record_har_content` (default: `'embed'`): HAR content mode (`'omit'`, `'embed'`, `'attach'`)
* `record_har_mode` (default: `'full'`): HAR recording mode (`'full'`, `'minimal'`)
## Advanced Options
* `disable_security` (default: `False`): ⚠️ **NOT RECOMMENDED** - Disables all browser security features
* `deterministic_rendering` (default: `False`): ⚠️ **NOT RECOMMENDED** - Forces consistent rendering but reduces performance
***
## Outdated BrowserProfile
For backward compatibility, you can pass all the parameters from above to the `BrowserProfile` and then to the `Browser`.
```python theme={null}
from browser_use import BrowserProfile
profile = BrowserProfile(headless=False)
browser = Browser(browser_profile=profile)
```
## Browser vs BrowserSession
`Browser` is an alias for `BrowserSession` - they are exactly the same class:
Use `Browser` for cleaner, more intuitive code.
***
## Class Methods
### `Browser.from_system_chrome()`
Creates a Browser instance using your system's Chrome installation and profile. Automatically detects Chrome executable and user data directory for your platform.
```python theme={null}
from browser_use import Browser
# Auto-select first available profile
browser = Browser.from_system_chrome()
# Or specify a profile
browser = Browser.from_system_chrome(profile_directory='Profile 1')
```
**Parameters:**
* `profile_directory` (default: `None`): Chrome profile to use (`'Default'`, `'Profile 1'`, etc.). If `None`, auto-selects the first available profile.
* `**kwargs`: Additional arguments passed to `Browser()` constructor (e.g., `headless=False`)
**Returns:** `Browser` instance configured to use your system Chrome
**Raises:** `RuntimeError` if Chrome is not found on your system
**Note:** You may need to fully close Chrome before using this, so Chrome profiles aren't used by multiple instances simultaneously.
***
### `Browser.list_chrome_profiles()`
Lists available Chrome profiles on the system.
```python theme={null}
from browser_use import Browser
profiles = Browser.list_chrome_profiles()
for p in profiles:
print(f"{p['directory']}: {p['name']}")
# Output:
# Profile 1: Work
# Profile 5: Personal
```
**Returns:** List of dicts with `'directory'` and `'name'` keys
**Example return value:**
```python theme={null}
[
{'directory': 'Default', 'name': 'Person 1'},
{'directory': 'Profile 1', 'name': 'Work'},
{'directory': 'Profile 5', 'name': 'Personal'}
]
```
# Authentication
Source: https://docs.browser-use.com/open-source/customize/browser/authentication
Log into websites using real browser profiles, saved storage state, and 2FA codes for authenticated automation.
Browser-Use supports multiple authentication strategies depending on your use case:
| Approach | Best For | Setup Effort |
| ------------------------------------------- | ------------------------------------ | ------------ |
| [Real Browser](#real-browser-profiles) | Personal automation, existing logins | Low |
| [Storage State](#storage-state-persistence) | Production, CI/CD, headless | Medium |
| [TOTP 2FA](#totp-2fa) | Sites with authenticator apps | Low |
| [Email and SMS 2FA](#email-and-sms-2fa) | Sites with email/SMS verification | Medium |
***
## Real Browser Profiles
Connect to your existing Chrome browser to reuse your authenticated sessions. No need to handle logins, cookies, or 2FA - if you are logged in on Chrome, the agent is, too.
See [Real Browser](/open-source/customize/browser/real-browser) for more details and platform paths.
You may need to close Chrome completely before running. Browser-Use launches Chrome in debug mode, which can conflict with existing Chrome processes.
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
# Auto-detect Chrome and profile (cross-platform)
browser = Browser.from_system_chrome()
agent = Agent(
task='Check my Gmail inbox',
browser=browser,
llm=ChatBrowserUse(),
)
await agent.run()
```
***
## Storage State Persistence
Export cookies and localStorage from an authenticated browser, then load them in headless mode. Useful for production/CI where you cannot use a real browser profile.
See [Browser Parameters](/open-source/customize/browser/all-parameters#user-data-&-profiles) for all storage options.
### Export from Real Browser
```python theme={null}
from browser_use import Browser
browser = Browser.from_system_chrome()
await browser.start()
await browser.export_storage_state('auth.json')
await browser.stop()
```
### Load in Headless Mode
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
browser = Browser(storage_state='auth.json')
agent = Agent(
task='Check my notifications',
browser=browser,
llm=ChatBrowserUse(),
)
await agent.run()
```
### Auto-Save and Load
When you provide a `storage_state` path, Browser-Use automatically:
* Loads cookies from the file on startup (if it exists)
* Saves cookies to the file periodically and on shutdown
```python theme={null}
browser = Browser(storage_state='session.json')
```
The file is created if it does not exist, and new cookies are merged with existing ones on each save.
### Storage State Format
The JSON file follows Playwright's format:
```json theme={null}
{
"cookies": [
{
"name": "session_id",
"value": "abc123",
"domain": ".example.com",
"path": "/",
"expires": 1704067200,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
}
],
"origins": [
{
"origin": "https://example.com",
"localStorage": [
{"name": "auth_token", "value": "xyz789"}
]
}
]
}
```
***
## TOTP 2FA
For sites using authenticator apps (Google Authenticator, 1Password, etc.), Browser-Use can generate TOTP codes automatically.
See [Sensitive Data](/open-source/examples/templates/sensitive-data) for more on credential handling.
### How It Works
1. Get the TOTP secret key when setting up 2FA (usually shown as "manual entry" or "cannot scan QR code")
2. Pass the secret with `bu_2fa_code` suffix in `sensitive_data`
3. When the agent inputs `bu_2fa_code`, it generates a fresh 6-digit code
```python theme={null}
from browser_use import Agent, ChatBrowserUse
# TOTP secret from your authenticator setup
# (NOT the 6-digit code - the secret key itself)
totp_secret = 'JBSWY3DPEHPK3PXP'
agent = Agent(
task='''
1. Go to https://example.com/login
2. Enter username x_user and password x_pass
3. When prompted for 2FA, enter bu_2fa_code
''',
sensitive_data={
'x_user': 'myusername',
'x_pass': 'mypassword',
'bu_2fa_code': totp_secret, # suffix must be bu_2fa_code
},
llm=ChatBrowserUse(),
)
await agent.run()
```
The placeholder name must end with `bu_2fa_code`. You can use any prefix: `google_bu_2fa_code`, `github_bu_2fa_code`, etc.
### 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
***
## Email and SMS 2FA
For sites that send verification codes via email or SMS, use follow-up tasks to retrieve the code.
### With AgentMail
[AgentMail](https://agentmail.to) provides disposable inboxes for email verification:
```python theme={null}
from agentmail import AsyncAgentMail
from browser_use import Agent, ChatBrowserUse, Tools, ActionResult
email_client = AsyncAgentMail()
inbox = await email_client.inboxes.create()
tools = Tools()
@tools.registry.action('Get email address for signup')
async def get_email_address():
return ActionResult(extracted_content=inbox.inbox_id)
@tools.registry.action('Get verification code from email')
async def get_verification_code():
emails = await email_client.inboxes.messages.list(inbox_id=inbox.inbox_id)
if emails.messages:
return ActionResult(extracted_content=emails.messages[0].text)
return ActionResult(error='No emails found')
agent = Agent(
task='Sign up at example.com, get verification code from email',
tools=tools,
llm=ChatBrowserUse(),
)
await agent.run()
```
See [`examples/integrations/agentmail/`](https://github.com/browser-use/browser-use/tree/main/examples/integrations/agentmail) for a more complete implementation with email waiting and parsing.
### With 1Password SDK
Retrieve codes from your password manager:
```python theme={null}
import os
from onepassword.client import Client
from browser_use import Agent, Tools, ActionResult, ChatBrowserUse
tools = Tools()
@tools.registry.action('Get 2FA code from 1Password', domains=['*.google.com'])
async def get_1password_2fa():
client = await Client.authenticate(
auth=os.environ['OP_SERVICE_ACCOUNT_TOKEN'],
integration_name='Browser-Use',
integration_version='v1.0.0',
)
code = await client.secrets.resolve('op://Private/Google/One-time passcode')
return ActionResult(extracted_content=code)
agent = Agent(
task='Login to Google and check email',
tools=tools,
llm=ChatBrowserUse(),
)
await agent.run()
```
See [`examples/custom-functions/onepassword_2fa.py`](https://github.com/browser-use/browser-use/tree/main/examples/custom-functions/onepassword_2fa.py) for the full example.
### With Gmail API
Built-in Gmail integration for reading 2FA codes from your inbox:
```python theme={null}
from browser_use import Agent, ChatBrowserUse, Tools
from browser_use.integrations.gmail import GmailService, register_gmail_actions
gmail_service = GmailService()
tools = Tools()
register_gmail_actions(tools, gmail_service=gmail_service)
agent = Agent(
task='Login to example.com, then get the verification code from Gmail',
tools=tools,
llm=ChatBrowserUse(),
)
await agent.run()
```
Requires Gmail API setup:
1. Enable Gmail API in [Google Cloud Console](https://console.cloud.google.com/)
2. Create OAuth 2.0 credentials (Desktop app)
3. Save credentials to `~/.config/browseruse/gmail_credentials.json`
See [`examples/integrations/gmail_2fa_integration.py`](https://github.com/browser-use/browser-use/tree/main/examples/integrations/gmail_2fa_integration.py) for setup with automatic credential validation.
***
## Security Best Practices
See [Secure Setup](/open-source/examples/templates/secure) for enterprise security with Azure OpenAI.
### Restrict Domains
Limit where the browser can navigate to prevent credential leaks:
```python theme={null}
browser = Browser(
allowed_domains=['*.example.com', 'auth.example.com'],
)
```
### Disable Vision for Sensitive Pages
Prevent screenshots from being sent to the LLM:
```python theme={null}
agent = Agent(
task='Login and check balance',
use_vision=False, # No screenshots sent to LLM
sensitive_data={'password': 'secret123'},
llm=ChatBrowserUse(),
)
```
### Domain-Specific Credentials
Route credentials to specific domains only:
```python theme={null}
sensitive_data = {
'https://*.work.com': {
'work_user': 'alice@work.com',
'work_pass': 'work_password',
},
'https://personal.com': {
'personal_user': 'alice@gmail.com',
'personal_pass': 'personal_password',
},
}
```
***
## Cloud Browser Profiles
For production deployments, consider [Browser Use Cloud](https://cloud.browser-use.com), which provides:
* Persistent browser profiles in the cloud
* Pre-authenticated sessions
* No local Chrome installation required
* Built-in proxy and fingerprint management
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
browser = Browser(cdp_url='wss://cloud.browser-use.com/...')
agent = Agent(
task='Check my orders',
browser=browser,
llm=ChatBrowserUse(),
)
await agent.run()
```
# Configuration
Source: https://docs.browser-use.com/open-source/customize/browser/basics
Configure the browser instance — headless mode, viewport size, proxy settings, and Playwright launch options.
***
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
browser = Browser(
headless=False, # Show browser window
window_size={'width': 1000, 'height': 700}, # Set window size
)
agent = Agent(
task='Search for Browser Use',
browser=browser,
llm=ChatBrowserUse(),
)
async def main():
await agent.run()
```
# Real Browser
Source: https://docs.browser-use.com/open-source/customize/browser/real-browser
Connect to your existing Chrome browser to preserve login sessions, cookies, and extensions for authenticated tasks.
This allows you to automate your existing Chrome browser, so you are already logged into your websites.
You may need to fully close Chrome before running these examples. Additionally, if Google search blocks automated browsers, use DuckDuckGo or other search engines instead.
## Basic Example
```python theme={null}
import asyncio
from browser_use import Agent, Browser, ChatOpenAI
# Auto-selects first available Chrome profile
browser = Browser.from_system_chrome()
agent = Agent(
task='Visit https://duckduckgo.com and search for "browser-use founders"',
browser=browser,
llm=ChatOpenAI(model='gpt-4.1-mini'),
)
async def main():
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
## Choosing a Profile
Chrome supports multiple profiles. List and select the one you want:
```python theme={null}
from browser_use import Browser
# List available profiles
profiles = Browser.list_chrome_profiles()
for p in profiles:
print(f"{p['directory']}: {p['name']}")
# Output:
# Profile 1: Work
# Profile 5: Personal
# Use a specific profile
browser = Browser.from_system_chrome(profile_directory='Profile 5')
```
## Manual Path Configuration
If auto-detection does not work, specify paths manually:
```python theme={null}
browser = Browser(
executable_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
user_data_dir='~/Library/Application Support/Google/Chrome',
profile_directory='Default',
)
```
## How it Works
`Browser.from_system_chrome()` automatically detects:
| Platform | Executable Path | User Data Directory |
| -------- | -------------------------------------------------------------- | --------------------------------------------- |
| macOS | `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` | `~/Library/Application Support/Google/Chrome` |
| Windows | `C:\Program Files\Google\Chrome\Application\chrome.exe` | `%LocalAppData%\Google\Chrome\User Data` |
| Linux | `/usr/bin/google-chrome` or `/usr/bin/chromium` | `~/.config/google-chrome` |
# Remote Browser
Source: https://docs.browser-use.com/open-source/customize/browser/remote
Connect to remote browsers via CDP — use with the Cloud, Browserbase, or any remote Chrome instance.
### Browser-Use Cloud Browser or CDP URL
The easiest way to use a cloud browser is with the built-in Browser-Use cloud service:
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
# Simple: Use Browser-Use cloud browser service
browser = Browser(
use_cloud=True, # Automatically provisions a cloud browser
)
# Advanced: Configure cloud browser parameters
# Managed browsers include CAPTCHA handling; some sites may still block automation
browser = Browser(
cloud_profile_id='your-profile-id', # Optional: specific browser profile
cloud_proxy_country_code='us', # Optional: proxy location (us, uk, fr, it, jp, au, de, fi, ca, in)
cloud_timeout=30, # Optional: requested session timeout in minutes (up to 240)
)
# Or use a CDP URL from any cloud browser provider
browser = Browser(
cdp_url="http://remote-server:9222" # Get a CDP URL from any provider
)
agent = Agent(
task="Your task here",
llm=ChatBrowserUse(),
browser=browser,
)
```
**Prerequisites:**
1. Get an API key from [cloud.browser-use.com](https://cloud.browser-use.com/new-api-key)
2. Set BROWSER\_USE\_API\_KEY environment variable
**Cloud Browser Parameters:**
* `cloud_profile_id`: UUID of a browser profile (optional, uses default if not specified)
* `cloud_proxy_country_code`: Country code for proxy location - supports: us, uk, fr, it, jp, au, de, fi, ca, in
* `cloud_timeout`: Requested session timeout in minutes, up to 240. Credit balance and account eligibility still apply; this is not a promise of free usage for that duration.
**Benefits:**
* ✅ No local browser setup required
* ✅ Scalable and fast cloud infrastructure
* ✅ Automatic provisioning and teardown
* ✅ Built-in authentication handling
* ✅ Optimized for browser automation
* ✅ Global proxy support for geo-restricted content
### Third-Party Cloud Browsers
You can pass in a CDP URL from any remote browser
### Proxy Connection
```python theme={null}
from browser_use import Agent, Browser, ChatBrowserUse
from browser_use.browser import ProxySettings
browser = Browser(
headless=False,
proxy=ProxySettings(
server="http://proxy-server:8080",
username="proxy-user",
password="proxy-pass"
),
cdp_url="http://remote-server:9222"
)
agent = Agent(
task="Your task here",
llm=ChatBrowserUse(),
browser=browser,
)
```
# Lifecycle Hooks
Source: https://docs.browser-use.com/open-source/customize/hooks
Hook into agent lifecycle events — before/after actions, navigation, extraction, and error handling callbacks.
Browser-Use provides lifecycle hooks that allow you to execute custom code at specific points during the agent's execution.
Hook functions can be used to read and modify agent state while running, implement custom logic, change configuration, integrate the Agent with external applications.
## Available Hooks
Currently, Browser-Use provides the following hooks:
| Hook | Description | When it is called |
| --------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `on_step_start` | Executed at the beginning of each agent step | Before the agent processes the current state and decides on the next action |
| `on_step_end` | Executed at the end of each agent step | After the agent has executed all the actions for the current step, before it starts the next step |
```python theme={null}
await agent.run(on_step_start=..., on_step_end=...)
```
Each hook should be an `async` callable function that accepts the `agent` instance as its only parameter.
### Basic Example
```python theme={null}
import asyncio
from pathlib import Path
from browser_use import Agent, ChatOpenAI
from browser_use.browser.events import ScreenshotEvent
async def my_step_hook(agent: Agent):
# inside a hook you can access all the state and methods under the Agent object:
# agent.settings, agent.state, agent.task
# agent.tools, agent.llm, agent.browser_session
# agent.pause(), agent.resume(), agent.add_new_task(...), etc.
# You also have direct access to the browser state
state = await agent.browser_session.get_browser_state_summary()
current_url = state.url
visit_log = agent.history.urls()
previous_url = visit_log[-2] if len(visit_log) >= 2 else None
print(f'Agent was last on URL: {previous_url} and is now on {current_url}')
cdp_session = await agent.browser_session.get_or_create_cdp_session()
# Example: Get page HTML content
doc = await cdp_session.cdp_client.send.DOM.getDocument(session_id=cdp_session.session_id)
html_result = await cdp_session.cdp_client.send.DOM.getOuterHTML(
params={'nodeId': doc['root']['nodeId']}, session_id=cdp_session.session_id
)
page_html = html_result['outerHTML']
# Example: Take a screenshot using the event system
screenshot_event = agent.browser_session.event_bus.dispatch(ScreenshotEvent(full_page=False))
await screenshot_event
result = await screenshot_event.event_result(raise_if_any=True, raise_if_none=True)
# Example: pause agent execution and resume it based on some custom code
if '/finished' in current_url:
agent.pause()
Path('result.txt').write_text(page_html)
input('Saved "finished" page content to result.txt, press [Enter] to resume...')
agent.resume()
async def main():
agent = Agent(
task='Search for the latest news about AI',
llm=ChatOpenAI(model='gpt-5-mini'),
)
await agent.run(
on_step_start=my_step_hook,
# on_step_end=...
max_steps=10,
)
if __name__ == '__main__':
asyncio.run(main())
```
## Data Available in Hooks
When working with agent hooks, you have access to the entire `Agent` instance. Here are some useful data points you can access:
* `agent.task` provides the main task, `agent.add_new_task(...)` allows you to queue up a new one
* `agent.tools` gives access to the `Tools()` object and `Registry()` containing the available actions
* `agent.tools.registry.execute_action('click', {'index': 123}, browser_session=agent.browser_session)`
* `agent.sensitive_data` contains the sensitive data dict, which can be updated in-place to add/remove/modify items
* `agent.settings` contains all the configuration options passed to the `Agent(...)` at init time
* `agent.llm` provides direct access to the main LLM object (e.g. `ChatOpenAI`)
* `agent.state` provides access to internal state, including agent thoughts, outputs, actions, and more
* `agent.history` gives access to historical data from the agent's execution:
* `agent.history.model_thoughts()`: Reasoning from Browser Use's model.
* `agent.history.model_outputs()`: Raw outputs from the Browser Use's model.
* `agent.history.model_actions()`: Actions taken by the agent
* `agent.history.extracted_content()`: Content extracted from web pages
* `agent.history.urls()`: URLs visited by the agent
* `agent.browser_session` provides direct access to the `BrowserSession` and CDP interface
* `agent.browser_session.agent_focus_target_id`: Get the current target ID the agent is focused on
* `agent.browser_session.get_or_create_cdp_session()`: Get the current CDP session for browser interaction
* `agent.browser_session.get_tabs()`: Get all tabs currently open
* `agent.browser_session.get_current_page_url()`: Get the URL of the current active tab
* `agent.browser_session.get_current_page_title()`: Get the title of the current active tab
## Tips for Using Hooks
* **Avoid blocking operations**: Since hooks run in the same execution thread as the agent, keep them efficient and avoid blocking operations.
* **Use custom tools instead**: Hooks are fairly advanced. Most use cases can be implemented with [custom tools](/open-source/customize/tools/basics) instead.
* **Increase step\_timeout**: If your hook is doing something that takes a long time, you can increase the `step_timeout` parameter in the `Agent(...)` constructor.
***
# MCP Server
Source: https://docs.browser-use.com/open-source/customize/integrations/mcp-server
Run browser-use as a local Model Context Protocol server. Connect AI models to browser automation through the MCP standard.
Browser Use can run as a local **Model Context Protocol (MCP)** server on your machine via stdio. This is the **free, open-source option** that gives you direct, low-level control over browser automation but requires your own LLM API keys.
Looking for a hosted solution? Use the [Cloud MCP Server](/cloud/guides/mcp-server) instead — no setup required, just an API key.
## Quick Start
```bash theme={null}
uvx --from 'browser-use[cli]' browser-use --mcp
```
The server will start in stdio mode, ready to accept MCP connections.
## Client Setup
```bash theme={null}
claude mcp add browser-use -- uvx --from 'browser-use[cli]' browser-use --mcp
```
Add to your Claude Desktop config file:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"browser-use": {
"command": "/Users/your-username/.local/bin/uvx",
"args": ["--from", "browser-use[cli]", "browser-use", "--mcp"],
"env": {
"OPENAI_API_KEY": "your-openai-api-key-here"
}
}
}
}
```
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"browser-use": {
"command": "uvx",
"args": ["--from", "browser-use[cli]", "browser-use", "--mcp"],
"env": {
"OPENAI_API_KEY": "your-openai-api-key-here"
}
}
}
}
```
Restart Claude Desktop after saving.
**macOS/Linux PATH Issue:** Claude Desktop may not find `uvx` in your PATH. Use the full path to `uvx` instead:
* Run `which uvx` in your terminal to find the location (usually `/Users/username/.local/bin/uvx` or `~/.local/bin/uvx`)
* Replace `"command": "uvx"` with the full path, e.g., `"command": "/Users/your-username/.local/bin/uvx"`
* Replace `your-username` with your actual username
**CLI Extras Required:** The `--from browser-use[cli]` flag installs the CLI extras needed for MCP server support.
Add to `~/.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"browser-use": {
"command": "uvx",
"args": ["--from", "browser-use[cli]", "browser-use", "--mcp"],
"env": {
"OPENAI_API_KEY": "your-openai-api-key-here"
}
}
}
}
```
Add to `~/.codeium/windsurf/mcp_config.json`:
```json theme={null}
{
"mcpServers": {
"browser-use": {
"command": "uvx",
"args": ["--from", "browser-use[cli]", "browser-use", "--mcp"],
"env": {
"OPENAI_API_KEY": "your-openai-api-key-here"
}
}
}
}
```
## Environment Variables
* `OPENAI_API_KEY` - Your OpenAI API key (required)
* `ANTHROPIC_API_KEY` - Your Anthropic API key (alternative to OpenAI)
* `BROWSER_USE_HEADLESS` - Set to `false` to show browser window
* `BROWSER_USE_DISABLE_SECURITY` - Set to `true` to disable browser security features
## Available Tools
The local MCP server exposes these low-level browser automation tools for direct control:
#### Autonomous Agent Tools
* **`retry_with_browser_use_agent`** - Run a complete browser automation task with an AI agent (use as last resort when direct control fails)
#### Direct Browser Control
* **`browser_navigate`** - Navigate to a URL
* **`browser_click`** - Click on an element by index
* **`browser_type`** - Type text into an element
* **`browser_get_state`** - Get current page state and interactive elements
* **`browser_scroll`** - Scroll the page
* **`browser_go_back`** - Go back in browser history
#### Tab Management
* **`browser_list_tabs`** - List all open browser tabs
* **`browser_switch_tab`** - Switch to a specific tab
* **`browser_close_tab`** - Close a tab
#### Content Extraction
* **`browser_extract_content`** - Extract structured content from the current page
* **`browser_get_html`** - Return the full page HTML or one CSS-selected element
* **`browser_screenshot`** - Capture the viewport or the full scrollable page
#### Session Management
* **`browser_list_sessions`** - List all active browser sessions with details
* **`browser_close_session`** - Close a specific browser session by ID
* **`browser_close_all`** - Close all active browser sessions
## Example Usage
Once configured, you can ask your AI to perform browser automation tasks:
```
"Please navigate to example.com and take a screenshot"
"Search for 'browser automation' on Google and summarize the first 3 results"
"Go to GitHub, find the browser-use repository, and tell me about the latest release"
```
## Programmatic Usage
You can also connect to the MCP server programmatically:
```python theme={null}
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def use_browser_mcp():
# Connect to browser-use MCP server
server_params = StdioServerParameters(
command="uvx",
args=["--from", "browser-use[cli]", "browser-use", "--mcp"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Navigate to a website
result = await session.call_tool(
"browser_navigate",
arguments={"url": "https://example.com"}
)
print(result.content[0].text)
# Get page state
result = await session.call_tool(
"browser_get_state",
arguments={"include_screenshot": True}
)
print("Page state retrieved!")
asyncio.run(use_browser_mcp())
```
## Troubleshooting
**"CLI addon is not installed" Error**
Make sure you are using `--from 'browser-use[cli]'` in your uvx command:
```bash theme={null}
uvx --from 'browser-use[cli]' browser-use --mcp
```
**"spawn uvx ENOENT" Error (macOS/Linux)**
Claude Desktop cannot find `uvx` in its PATH. Use the full path in your config:
* Run `which uvx` in terminal to find the location
* Update your config to use the full path (e.g., `/Users/your-username/.local/bin/uvx`)
**Browser doesn't start**
* Check that you have Chrome/Chromium installed
* Try setting `BROWSER_USE_HEADLESS=false` to see browser window
* Ensure no other browser instances are using the same profile
**API Key Issues**
* Verify your `OPENAI_API_KEY` is set correctly
* Check API key permissions and billing status
* Try using `ANTHROPIC_API_KEY` as an alternative
**Connection Issues in Claude Desktop**
* Restart Claude Desktop after config changes
* Check the config file syntax is valid JSON
* Verify the file path is correct for your OS
* Check logs at `~/Library/Logs/Claude/` (macOS) or `%APPDATA%\Claude\Logs\` (Windows)
**Debug Mode**
Enable debug logging by setting:
```bash theme={null}
export BROWSER_USE_LOGGING_LEVEL=DEBUG
uvx --from 'browser-use[cli]' browser-use --mcp
```
## Security Considerations
* The MCP server has access to your browser and file system
* Only connect trusted MCP clients
* Be cautious with sensitive websites and data
* Consider running in a sandboxed environment for untrusted automation
## Next Steps
* Explore the [examples directory](https://github.com/browser-use/browser-use/tree/main/examples) for more usage patterns
* Check out [MCP documentation](https://modelcontextprotocol.io/) to learn more about the protocol
* Join our [Discord](https://link.browser-use.com/discord) for support and discussions
# Add Tools
Source: https://docs.browser-use.com/open-source/customize/tools/add
Extend agents with custom Python functions. Add API calls, file operations, or any custom logic as agent tools.
Examples:
* deterministic clicks
* file handling
* calling APIs
* human-in-the-loop
* browser interactions
* calling LLMs
* get 2fa codes
* send emails
* Playwright integration (see [GitHub example](https://github.com/browser-use/browser-use/blob/main/examples/browser/playwright_integration.py))
* ...
Simply add `@tools.action(...)` to your function.
```python theme={null}
from browser_use import Tools, Agent, ActionResult
tools = Tools()
@tools.action(description='Ask human for help with a question')
async def ask_human(question: str) -> ActionResult:
answer = input(f'{question} > ')
return ActionResult(extracted_content=f'The human responded with: {answer}')
```
```python theme={null}
agent = Agent(task='...', llm=llm, tools=tools)
```
* **`description`** *(required)* - What the tool does, the LLM uses this to decide when to call it.
* **`allowed_domains`** - List of domains where tool can run (e.g. `['*.example.com']`), defaults to all domains
The Agent fills your function parameters based on their names, type hints, & defaults.
**Common Pitfall**: Parameter names must match exactly! Use `browser_session: BrowserSession` (not `browser: Browser`).
The agent injects special parameters by **name matching**, so using incorrect names will cause your tool to fail silently.
See [Available Objects](#available-objects) below for the correct parameter names.
## Available Objects
Your function has access to these objects:
* **`browser_session: BrowserSession`** - Current browser session for CDP access
* **`cdp_client`** - Direct Chrome DevTools Protocol client
* **`page_extraction_llm: BaseChatModel`** - The LLM you pass into agent. This can be used to do a custom llm call here.
* **`file_system: FileSystem`** - File system access
* **`available_file_paths: list[str]`** - Available files for upload/processing
* **`has_sensitive_data: bool`** - Whether action contains sensitive data
## Browser Interaction Examples
You can use `browser_session` to directly interact with page elements using CSS selectors:
```python theme={null}
from browser_use import Tools, Agent, ActionResult, BrowserSession
tools = Tools()
@tools.action(description='Click the submit button using CSS selector')
async def click_submit_button(browser_session: BrowserSession):
# Get the current page
page = await browser_session.must_get_current_page()
# Get element(s) by CSS selector
elements = await page.get_elements_by_css_selector('button[type="submit"]')
if not elements:
return ActionResult(extracted_content='No submit button found')
# Click the first matching element
await elements[0].click()
return ActionResult(extracted_content='Submit button clicked!')
```
Available methods on `Page`:
* `get_elements_by_css_selector(selector: str)` - Returns list of matching elements
* `get_element_by_prompt(prompt: str, llm)` - Returns element or None using LLM
* `must_get_element_by_prompt(prompt: str, llm)` - Returns element or raises error
Available methods on `Element`:
* `click()` - Click the element
* `type(text: str)` - Type text into the element
* `get_text()` - Get element text content
* See `browser_use/actor/element.py` for more methods
## Pydantic Input
You can use Pydantic for the tool parameters:
```python theme={null}
import json
from pydantic import BaseModel, Field
from browser_use import Tools
tools = Tools()
class Cars(BaseModel):
name: str = Field(description='The name of the car, e.g. "Toyota Camry"')
price: int = Field(description='The price of the car as int in USD, e.g. 25000')
@tools.action(description='Save cars to file')
def save_cars(cars: list[Cars]) -> str:
with open('cars.json', 'w') as f:
json.dump(cars, f)
return f'Saved {len(cars)} cars to file'
task = "find cars and save them to file"
```
## Domain Restrictions
Limit tools to specific domains:
```python theme={null}
@tools.action(
description='Fill out banking forms',
allowed_domains=['https://mybank.com']
)
def fill_bank_form(account_number: str) -> str:
# Only works on mybank.com
return f'Filled form for account {account_number}'
```
## Advanced Example
For a comprehensive example of custom tools with Playwright integration, see:
**[Playwright Integration Example](https://github.com/browser-use/browser-use/blob/main/examples/browser/playwright_integration.py)**
This shows how to create custom actions that use Playwright's precise browser automation alongside Browser-Use.
## Common Pitfalls
The agent injects special parameters **by name**, not by type. Using incorrect parameter names is the most common cause of tools failing silently.
### ❌ Wrong: Using `browser: Browser`
```python theme={null}
from browser_use import Tools, ActionResult, Browser
@tools.action('My action')
def my_action(browser: Browser) -> ActionResult: # WRONG!
# This will NOT receive the browser session
pass
```
### ✅ Correct: Using `browser_session: BrowserSession`
```python theme={null}
from browser_use import Tools, ActionResult, BrowserSession
@tools.action('My action')
async def my_action(browser_session: BrowserSession) -> ActionResult: # CORRECT!
page = await browser_session.must_get_current_page()
# Now you have access to the browser
return ActionResult(extracted_content='Done')
```
### Key Points
1. **Use `browser_session: BrowserSession`** - not `browser: Browser`
2. **Use `async` functions** - recommended for consistency with browser operations
3. **Return `ActionResult`** - not plain strings (though strings work, `ActionResult` provides more control)
4. **Parameter names must match exactly** - see [Available Objects](#available-objects) for the full list of injectable parameters
# Available Tools
Source: https://docs.browser-use.com/open-source/customize/tools/available
Built-in tools — click, type, scroll, extract, navigate, and more. Full list of default agent actions.
### Navigation & Browser Control
* **`search`** - Search queries (DuckDuckGo, Google, Bing)
* **`navigate`** - Navigate to URLs
* **`go_back`** - Go back in browser history
* **`wait`** - Wait for specified seconds
### Page Interaction
* **`click`** - Click elements by their index
* **`input`** - Input text into form fields
* **`upload_file`** - Upload files to file inputs
* **`scroll`** - Scroll the page up/down
* **`find_text`** - Scroll to specific text on page
* **`send_keys`** - Send special keys (Enter, Escape, etc.)
### JavaScript Execution
* **`evaluate`** - Execute custom JavaScript code on the page (for advanced interactions, shadow DOM, custom selectors, data extraction)
### Tab Management
* **`switch`** - Switch between browser tabs
* **`close`** - Close browser tabs
### Content Extraction
* **`extract`** - Extract data from webpages using LLM
### Visual Analysis
* **`screenshot`** - Request a screenshot in your next browser state for visual confirmation
### Form Controls
* **`dropdown_options`** - Get dropdown option values
* **`select_dropdown`** - Select dropdown options
### File Operations
* **`write_file`** - Write content to files
* **`read_file`** - Read file contents
* **`replace_file`** - Replace text in files
### Task Completion
* **`done`** - Complete the task (always available)
# Overview
Source: https://docs.browser-use.com/open-source/customize/tools/basics
Learn about built-in browser actions and custom tools.
## Quick Example
```python theme={null}
from browser_use import Agent, Tools, ActionResult, BrowserSession
tools = Tools()
@tools.action('Ask human for help with a question')
async def ask_human(question: str, browser_session: BrowserSession) -> ActionResult:
answer = input(f'{question} > ')
return ActionResult(extracted_content=f'The human responded with: {answer}')
agent = Agent(
task='Ask human for help',
llm=llm,
tools=tools,
)
```
**Important**: The parameter must be named exactly `browser_session` with type `BrowserSession` (not `browser: Browser`).
The agent injects parameters by name matching, so using the wrong name will cause your tool to fail silently.
Use `browser_session` parameter in tools for deterministic [Actor](/open-source/legacy/actor/basics) actions.
# Remove Tools
Source: https://docs.browser-use.com/open-source/customize/tools/remove
Exclude default tools to restrict agent capabilities.
```python theme={null}
from browser_use import Tools
tools = Tools(exclude_actions=['search', 'wait'])
agent = Agent(task='...', llm=llm, tools=tools)
```
# Response Format
Source: https://docs.browser-use.com/open-source/customize/tools/response
Customize how tools return data to the agent. Control response formatting, extraction, and content filtering.
Tools return results using `ActionResult` or simple strings.
## Return Types
```python theme={null}
@tools.action('My tool')
def my_tool() -> str:
return "Task completed successfully"
@tools.action('Advanced tool')
def advanced_tool() -> ActionResult:
return ActionResult(
extracted_content="Main result",
long_term_memory="Remember this info",
error="Something went wrong",
is_done=True,
success=True,
attachments=["file.pdf"],
)
```
## ActionResult Properties
* `extracted_content` (default: `None`) - Main result passed to LLM, this is equivalent to returning a string.
* `include_extracted_content_only_once` (default: `False`) - Set to `True` for large content to include it only once in the LLM input.
* `long_term_memory` (default: `None`) - This is always included in the LLM input for all future steps.
* `error` (default: `None`) - Error message, we catch exceptions and set this automatically. This is always included in the LLM input.
* `is_done` (default: `False`) - Tool completes entire task
* `success` (default: `None`) - Task success (only valid with `is_done=True`)
* `attachments` (default: `None`) - Files to show user
* `metadata` (default: `None`) - Debug/observability data
## Why `extracted_content` and `long_term_memory`?
With this you control the context for the LLM.
### 1. Include short content always in context
```python theme={null}
def simple_tool() -> str:
return "Hello, world!" # Keep in context for all future steps
```
### 2. Show long content once, remember subset in context
```python theme={null}
return ActionResult(
extracted_content="[500 lines of product data...]", # Shows to LLM once
include_extracted_content_only_once=True, # Never show full output again
long_term_memory="Found 50 products" # Only this in future steps
)
```
We save the full `extracted_content` to files which the LLM can read in future steps.
### 3. Don't show long content, remember subset in context
```python theme={null}
return ActionResult(
extracted_content="[500 lines of product data...]", # The LLM never sees this because `long_term_memory` overrides it and `include_extracted_content_only_once` is not used
long_term_memory="Saved user's favorite products", # This is shown to the LLM in future steps
)
```
## Terminating the Agent
Set `is_done=True` to stop the agent completely. Use when your tool finishes the entire task:
```python theme={null}
@tools.action(description='Complete the task')
def finish_task() -> ActionResult:
return ActionResult(
extracted_content="Task completed!",
is_done=True, # Stops the agent
success=True # Task succeeded
)
```
# Get Help
Source: https://docs.browser-use.com/open-source/development/get-help
Get help — join 20k+ developers on Discord, search GitHub issues, or ask the community.
1. Check our [GitHub Issues](https://github.com/browser-use/browser-use/issues)
2. Ask in our [Discord community](https://link.browser-use.com/discord)
3. Get support for your enterprise with [support@browser-use.com](mailto:support@browser-use.com)
# Costs
Source: https://docs.browser-use.com/open-source/development/monitoring/costs
Track token usage and API costs for your browser automation tasks
## Cost Tracking
To track token usage and costs, enable cost calculation:
```python theme={null}
from browser_use import Agent, ChatBrowserUse
agent = Agent(
task="Search for latest news about AI",
llm=ChatBrowserUse(),
calculate_cost=True # Enable cost tracking
)
history = await agent.run()
# Get usage from history
print(f"Token usage: {history.usage}")
# Or get from usage summary
usage_summary = await agent.token_cost_service.get_usage_summary()
print(f"Usage summary: {usage_summary}")
```
# Observability
Source: https://docs.browser-use.com/open-source/development/monitoring/observability
Trace agent execution steps and capture browser session recordings.
## Overview
Browser Use has a native integration with [Laminar](https://laminar.sh) - open-source platform for monitoring and analyzing error patterns in AI agents.
Laminar SDK automatically captures **agent execution steps, costs and browser session recordings** of Browser Use agent.
Browser session recordings allow developers to see full video replay of the browser session, which is useful for debugging Browser Use agent.
## Setup
Install Laminar python SDK.
```bash theme={null}
pip install lmnr
```
Register on [Laminar Cloud](https://laminar.sh) or [self-host Laminar](https://github.com/lmnr-ai/lmnr), create a project and get the project API key from your project settings. Set the `LMNR_PROJECT_API_KEY` environment variable.
```bash theme={null}
export LMNR_PROJECT_API_KEY=
```
## Usage
Initialize Laminar at the top of your project, and both Browser Use agent traces and session recordings will be automatically captured.
```python {7-9} theme={null}
from browser_use import Agent, ChatGoogle
import asyncio
from lmnr import Laminar
import os
# At initialization time, Laminar auto-instruments
# Browser Use and any browser you use (local or remote)
Laminar.initialize(project_api_key=os.getenv('LMNR_PROJECT_API_KEY'))
async def main():
agent = Agent(
task="go to ycombinator.com, summarize 3 startups from the latest batch",
llm=ChatGoogle(model="gemini-2.5-flash"),
)
await agent.run()
asyncio.run(main())
```
## Viewing Traces
You can view traces in the Laminar UI by going to the traces tab in your project.
When you select a trace, you can see both the browser session recording and the agent execution steps.
Timeline of the browser session is synced with the agent execution steps.
In the trace view, you can also see the agent's current step, the tool it is using, and the tool's input and output.
## Laminar
To learn more about how you can trace and evaluate your Browser Use agent with Laminar, check out [Laminar docs](https://docs.lmnr.ai).
## Browser Use Cloud Authentication
Browser Use can sync your agent runs to the cloud for easy viewing and sharing. Authentication is required to protect your data.
### Quick Setup
```bash theme={null}
# Authenticate once to enable cloud sync for all future runs
browser-use auth
# Or if using module directly:
python -m browser_use.cli auth
```
**Note**: Cloud sync is enabled by default. If you have disabled it, you can re-enable with `export BROWSER_USE_CLOUD_SYNC=true`.
### Manual Authentication
```python theme={null}
# Authenticate from code after task completion
from browser_use import Agent
agent = Agent(task="your task")
await agent.run()
# Later, authenticate for future runs
await agent.authenticate_cloud_sync()
```
### Reset Authentication
```bash theme={null}
# Force re-authentication with a different account
rm ~/.config/browseruse/cloud_auth.json
browser-use auth
```
**Note**: Authentication uses OAuth Device Flow - you must complete the auth process while the command is running. Links expire when the polling stops.
# OpenLIT
Source: https://docs.browser-use.com/open-source/development/monitoring/openlit
Complete observability with OpenLIT tracing.
## Overview
Browser Use has native integration with [OpenLIT](https://github.com/openlit/openlit) - an open-source opentelemetry-native platform that provides complete, granular traces for every task your browser-use agent performs—from high-level agent invocations down to individual browser actions.
Read more about OpenLIT in the [OpenLIT docs](https://docs.openlit.io).
## Setup
Install OpenLIT alongside Browser Use:
```bash theme={null}
pip install openlit browser-use
```
## Usage
OpenLIT provides automatic, comprehensive instrumentation with **zero code changes** beyond initialization:
```python {5-6} theme={null}
from browser_use import Agent, Browser, ChatOpenAI
import asyncio
import openlit
# Initialize OpenLIT - that's it!
openlit.init()
async def main():
browser = Browser()
llm = ChatOpenAI(
model="gpt-4o",
)
agent = Agent(
task="Find the number trending post on Hacker news",
llm=llm,
browser=browser,
)
history = await agent.run()
return history
if __name__ == "__main__":
history = asyncio.run(main())
```
## Viewing Traces
OpenLIT provides a powerful dashboard where you can:
### Monitor Execution Flows
See the complete execution tree with timing information for every span. Click on any `invoke_model` span to see the exact prompt sent to the LLM and the complete response with agent reasoning.
### Track Costs and Token Usage
* Cost breakdown by agent, task, and model
* Token usage per LLM call with full input/output visibility
* Compare costs across different LLM providers
* Identify expensive prompts and optimize them
### Debug Failures with Agent Thoughts
When an automation fails, you can:
* See exactly which step failed
* Read the agent's thinking at the failure point
* Check the browser state and available elements
* Analyze whether the failure was due to bad reasoning or bad information
* Fix the root cause with full context
### Performance Optimization
* Identify slow steps (LLM calls vs browser actions vs HTTP requests)
* Compare execution times across runs
* Optimize max\_steps and max\_actions\_per\_step
* Track HTTP request latency for page navigations
## Configuration
### Custom OpenTelemetry Endpoint Configuration
```python theme={null}
import openlit
# Configure custom OTLP endpoints
openlit.init(
otlp_endpoint="http://localhost:4318",
application_name="my-browser-automation",
environment="production"
)
```
### Environment Variables
You can also configure OpenLIT via environment variables:
```bash theme={null}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_SERVICE_NAME="browser-automation"
export OTEL_ENVIRONMENT="production"
```
### Self-Hosted OpenLIT
If you prefer to keep your data on-premises:
```bash theme={null}
# Using Docker
docker run -d \
-p 4318:4318 \
-p 3000:3000 \
openlit/openlit:latest
# Access dashboard at http://localhost:3000
```
## Integration with Existing Tools
OpenLIT uses OpenTelemetry internally, so it integrates seamlessly with:
* **Jaeger** - Distributed tracing visualization
* **Prometheus** - Metrics collection and alerting
* **Grafana** - Custom dashboards and analytics
* **Datadog** - APM and log management
* **New Relic** - Full-stack observability
* **Elastic APM** - Application performance monitoring
Simply configure OpenLIT to export to your existing OTLP-compatible endpoint.
# Telemetry
Source: https://docs.browser-use.com/open-source/development/monitoring/telemetry
Understanding Browser Use's telemetry
## Overview
Browser Use is free under the MIT license. To help us continue improving the library, we collect usage telemetry with [PostHog](https://posthog.com). Telemetry may include usage metadata and task-level data such as task instructions, URLs visited, action traces, errors, and final results. We may use this information to operate, debug, secure, analyze, develop, and improve Browser Use and related offerings, including by creating aggregated, de-identified, or sanitized datasets and workflow patterns in accordance with our Terms of Service and Privacy Policy.
## Opting Out
You can disable telemetry by setting the environment variable:
```bash .env theme={null}
ANONYMIZED_TELEMETRY=false
```
Or in your Python code:
```python theme={null}
import os
os.environ["ANONYMIZED_TELEMETRY"] = "false"
```
Even when enabled, telemetry has zero impact on the library's performance. Code is available in [Telemetry
Service](https://github.com/browser-use/browser-use/tree/main/browser_use/telemetry).
## Legal
By using Browser Use services, you agree to our [Terms of Service](https://browser-use.com/legal/terms-of-service) and
[Privacy Policy](https://browser-use.com/privacy/).
# Contribution Guide
Source: https://docs.browser-use.com/open-source/development/setup/contribution-guide
How to contribute — code standards, PR process, testing requirements, and submitting your first pull request.
## Mission
* Make developers happy
* Do more clicks than human
* Tell your computer what to do, and it gets it done.
* Make agents faster and more reliable.
## What to work on?
* This space is moving fast. We have 10 ideas daily. Let us exchange some.
* Browse our [GitHub Issues](https://github.com/browser-use/browser-use/issues)
* Check out our most active issues on [Discord](https://discord.gg/zXJJHtJf3k)
* Get inspiration in [`#showcase-your-work`](https://discord.com/channels/1303749220842340412/1305549200678850642) channel
## What makes a great PR?
1. Why do we need this PR?
2. Include a demo screenshot/gif
3. Make sure the PR passes all CI tests
4. Keep your PR focused on a single feature
## How?
1. Fork the repository
2. Create a new branch for your feature
3. Submit a PR
We are overwhelmed with Issues. Feel free to bump your issues/PRs with comments periodically if you need faster feedback.
# Local Development Setup
Source: https://docs.browser-use.com/open-source/development/setup/local-setup
Set up for local development. Clone the repo, install dependencies, and run tests to start contributing.
## Welcome to Browser Use Development!
```bash theme={null}
git clone https://github.com/browser-use/browser-use
cd browser-use
uv sync --all-extras --dev
# or pip install -U git+https://github.com/browser-use/browser-use.git@main
```
## Configuration
Set up your environment variables:
```bash theme={null}
# Copy the example environment file
cp .env.example .env
# set logging level
# BROWSER_USE_LOGGING_LEVEL=debug
```
## Helper Scripts
For common development tasks
```bash theme={null}
# Complete setup script - installs uv, creates a venv, and installs dependencies
./bin/setup.sh
# Run all pre-commit hooks (formatting, linting, type checking)
./bin/lint.sh
# Run the core test suite that's executed in CI
./bin/test.sh
```
## Run examples
```bash theme={null}
uv run examples/simple.py
```
# Ad-Use (Ad Generator)
Source: https://docs.browser-use.com/open-source/examples/apps/ad-use
Generate Instagram image ads and TikTok video ads from landing pages using browser agents, Google's Nano Banana 🍌, and Veo3.
This demo requires browser-use v0.7.6+.
## Features
1. Agent visits your target website
2. Captures brand name, tagline, and key selling points
3. Takes a clean screenshot for design reference
4. Creates scroll-stopping Instagram image ads with 🍌
5. Generates viral TikTok video ads with Veo3
6. Supports parallel generation of multiple ads
## Setup
Make sure the newest version of browser-use is installed (with screenshot functionality):
```bash theme={null}
pip install -U browser-use
```
Export your Gemini API key, get it from: [Google AI Studio](https://makersuite.google.com/app/apikey)
```
export GOOGLE_API_KEY='your-google-api-key-here'
```
Clone the repo and cd into the app folder
```bash theme={null}
git clone https://github.com/browser-use/browser-use.git
cd browser-use/examples/apps/ad-use
```
## Normal Usage
```bash theme={null}
# Basic - Generate Instagram image ad (default)
python ad_generator.py --url https://www.apple.com/iphone-16-pro/
# Generate TikTok video ad with Veo3
python ad_generator.py --tiktok --url https://www.apple.com/iphone-16-pro/
# Generate multiple ads in parallel
python ad_generator.py --instagram --count 3 --url https://www.apple.com/iphone-16-pro/
python ad_generator.py --tiktok --count 2 --url https://www.apple.com/iphone-16-pro/
# Debug Mode - See the browser in action
python ad_generator.py --url https://www.apple.com/iphone-16-pro/ --debug
```
## Command Line Options
* `--url`: Landing page URL to analyze
* `--instagram`: Generate Instagram image ad (default if no flag specified)
* `--tiktok`: Generate TikTok video ad using Veo3
* `--count N`: Generate N ads in parallel (default: 1)
* `--debug`: Show browser window and enable verbose logging
## Programmatic Usage
```python theme={null}
import asyncio
from ad_generator import create_ad_from_landing_page
async def main():
results = await create_ad_from_landing_page(
url="https://your-landing-page.com",
debug=False
)
print(f"Generated ads: {results}")
asyncio.run(main())
```
## Output
Generated ads are saved in the `output/` directory with:
* **PNG image files** (ad\_timestamp.png) - Instagram ads generated with Gemini 2.5 Flash Image
* **MP4 video files** (ad\_timestamp.mp4) - TikTok ads generated with Veo3
* **Analysis files** (analysis\_timestamp.txt) - Browser agent analysis and prompts used
* **Landing page screenshots** (landing\_page\_timestamp.png) - Reference screenshots
## Source Code
Full implementation: [https://github.com/browser-use/browser-use/tree/main/examples/apps/ad-use](https://github.com/browser-use/browser-use/tree/main/examples/apps/ad-use)
# Msg-Use (WhatsApp Sender)
Source: https://docs.browser-use.com/open-source/examples/apps/msg-use
AI-powered WhatsApp message scheduler using browser agents and Gemini. Schedule personalized messages in natural language.
This demo requires browser-use v0.7.7+.
## Features
1. Agent logs into WhatsApp Web automatically
2. Parses natural language scheduling instructions
3. Composes personalized messages using AI
4. Schedules messages for future delivery or sends immediately
5. Persistent session (no repeated QR scanning)
## Setup
Make sure the newest version of browser-use is installed:
```bash theme={null}
pip install -U browser-use
```
Export your Gemini API key, get it from: [Google AI Studio](https://makersuite.google.com/app/apikey)
```bash theme={null}
export GOOGLE_API_KEY='your-gemini-api-key-here'
```
Clone the repo and cd into the app folder
```bash theme={null}
git clone https://github.com/browser-use/browser-use.git
cd browser-use/examples/apps/msg-use
```
## Initial Login
First-time setup requires QR code scanning:
```bash theme={null}
python login.py
```
* Scan QR code when browser opens
* Session will be saved for future use
## Normal Usage
1. **Edit your schedule** in `messages.txt`:
```
- Send "Hi" to Magnus on the 13.06 at 18:15
- Tell hinge date (Camila) at 20:00 that I miss her
- Send happy birthday message to sister on the 15.06
- Remind mom to pick up the car next tuesday
```
2. **Test mode** - See what will be sent:
```bash theme={null}
python scheduler.py --test
```
3. **Run scheduler**:
```bash theme={null}
python scheduler.py
# Debug Mode - See the browser in action
python scheduler.py --debug
# Auto Mode - Respond to unread messages every ~30 minutes
python scheduler.py --auto
```
## Programmatic Usage
```python theme={null}
import asyncio
from scheduler import schedule_messages
async def main():
messages = [
"Send hello to John at 15:30",
"Remind Sarah about meeting tomorrow at 9am"
]
await schedule_messages(messages, debug=False)
asyncio.run(main())
```
## Example Output
The scheduler processes natural language and outputs structured results:
```json theme={null}
[
{
"contact": "Magnus",
"original_message": "Hi",
"composed_message": "Hi",
"scheduled_time": "2025-06-13 18:15"
},
{
"contact": "Camila",
"original_message": "I miss her",
"composed_message": "I miss you ❤️",
"scheduled_time": "2025-06-14 20:00"
},
{
"contact": "sister",
"original_message": "happy birthday message",
"composed_message": "Happy birthday! 🎉 Wishing you an amazing day, sis! Hope you have the best birthday ever! ❤️🎂🎈",
"scheduled_time": "2025-06-15 09:00"
}
]
```
## Source Code
Full implementation: [https://github.com/browser-use/browser-use/tree/main/examples/apps/msg-use](https://github.com/browser-use/browser-use/tree/main/examples/apps/msg-use)
# News-Use (News Monitor)
Source: https://docs.browser-use.com/open-source/examples/apps/news-use
Monitor news websites and extract articles with sentiment analysis using browser agents and Google Gemini.
This demo requires browser-use v0.7.7+.
## Features
1. Agent visits any news website automatically
2. Finds and clicks the most recent headline article
3. Extracts title, URL, posting time, and full content
4. Generates short/long summaries with sentiment analysis
5. Persistent deduplication across monitoring sessions
## Setup
Make sure the newest version of browser-use is installed:
```bash theme={null}
pip install -U browser-use
```
Export your Gemini API key, get it from: [Google AI Studio](https://makersuite.google.com/app/apikey)
```bash theme={null}
export GOOGLE_API_KEY='your-google-api-key-here'
```
Clone the repo, cd to the app
```bash theme={null}
git clone https://github.com/browser-use/browser-use.git
cd browser-use/examples/apps/news-use
```
## Usage Examples
```bash theme={null}
# One-time extraction - Get the latest article and exit
python news_monitor.py --once
# Monitor Bloomberg continuously (default)
python news_monitor.py
# Monitor TechCrunch every 60 seconds
python news_monitor.py --url https://techcrunch.com --interval 60
# Debug mode - See browser in action
python news_monitor.py --once --debug
```
## Output Format
Articles are displayed with timestamp, sentiment emoji, and summary:
```
[2025-09-11 02:49:21] - 🟢 - Klarna's IPO raises $1.4B, benefiting existing investors
[2025-09-11 02:54:15] - 🔴 - Tech layoffs continue as major firms cut workforce
[2025-09-11 02:59:33] - 🟡 - Federal Reserve maintains interest rates unchanged
```
**Sentiment Indicators:**
* 🟢 **Positive** - Good news, growth, success stories
* 🟡 **Neutral** - Factual reporting, announcements, updates
* 🔴 **Negative** - Challenges, losses, negative events
## Data Persistence
All extracted articles are saved to `news_data.json` with complete metadata:
```json theme={null}
{
"hash": "a1b2c3d4...",
"pulled_at": "2025-09-11T02:49:21Z",
"data": {
"title": "Klarna's IPO pops, raising $1.4B",
"url": "https://techcrunch.com/2025/09/11/klarna-ipo/",
"posting_time": "12:11 PM PDT · September 10, 2025",
"short_summary": "Klarna's IPO raises $1.4B, benefiting existing investors like Sequoia.",
"long_summary": "Fintech Klarna successfully IPO'd on the NYSE...",
"sentiment": "positive"
}
}
```
## Programmatic Usage
```python theme={null}
import asyncio
from news_monitor import extract_latest_article
async def main():
# Extract latest article from any news site
result = await extract_latest_article(
site_url="https://techcrunch.com",
debug=False
)
if result["status"] == "success":
article = result["data"]
print(f"📰 {article['title']}")
print(f"😊 Sentiment: {article['sentiment']}")
print(f"📝 Summary: {article['short_summary']}")
asyncio.run(main())
```
## Advanced Configuration
```python theme={null}
# Custom monitoring with filters
async def monitor_with_filters():
while True:
result = await extract_latest_article("https://bloomberg.com")
if result["status"] == "success":
article = result["data"]
# Only alert on negative market news
if article["sentiment"] == "negative" and "market" in article["title"].lower():
send_alert(article)
await asyncio.sleep(300) # Check every 5 minutes
```
## Source Code
Full implementation: [https://github.com/browser-use/browser-use/tree/main/examples/apps/news-use](https://github.com/browser-use/browser-use/tree/main/examples/apps/news-use)
# Vibetest-Use (Automated QA)
Source: https://docs.browser-use.com/open-source/examples/apps/vibetest-use
Run multi-agent Browser-Use tests to catch UI bugs, broken links, and accessibility issues before they ship.
Requires **browser-use \< v0.5.0** and Playwright Chromium. Currently getting an update to v0.7.6+.
## Features
1. Launches multiple headless (or visible) Browser-Use agents in parallel
2. Crawls your site and records screenshots, broken links & a11y issues
3. Works on production URLs *and* `localhost` dev servers
4. Simple natural-language prompts via MCP in Cursor / Claude Code
## Quick Start
```bash theme={null}
# 1. Clone repo
git clone https://github.com/browser-use/vibetest-use.git
cd vibetest-use
# 2. Create & activate env
uv venv --python 3.11
source .venv/bin/activate
# 3. Install project
uv pip install -e .
# 4. Install browser runtime once
uvx browser-use install
```
### 1) Claude Code
```bash theme={null}
# Register the MCP server
claude mcp add vibetest /full/path/to/vibetest-use/.venv/bin/vibetest-mcp \
-e GOOGLE_API_KEY="your_api_key"
# Inside a Claude chat
> /mcp
# ⎿ MCP Server Status
# • vibetest: connected
```
### 2) Cursor (manual MCP entry)
1. Open **Settings → MCP**
2. Click **Add Server** and paste:
```json theme={null}
{
"mcpServers": {
"vibetest": {
"command": "/full/path/to/vibetest-use/.venv/bin/vibetest-mcp",
"env": {
"GOOGLE_API_KEY": "your_api_key"
}
}
}
}
```
## Basic Prompts
```
> Vibetest my website with 5 agents: browser-use.com
> Run vibetest on localhost:3000
> Run a headless vibetest on localhost:8080 with 10 agents
```
### Parameters
* **URL** – any `https` or `http` host or `localhost:port`
* **Agents** – `3` by default; more agents = deeper coverage
* **Headless** – say *headless* to hide the browser, omit to watch it live
## Requirements
* Python 3.11+
* Google API key (Gemini flash used for analysis)
* Cursor / Claude with MCP support
## Source Code
Full implementation: [https://github.com/browser-use/vibetest-use](https://github.com/browser-use/vibetest-use)
# browser-use
Source: https://docs.browser-use.com/open-source/examples/skills/browser-use
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the agent needs to navigate websites, interact with web pages, fill forms, take screenshots, or extract information.
The `browser-use` skill teaches your agent the [Browser Use CLI](/open-source/browser-use-cli). The CLI runs Python in the browser to do actions online. The agent can connect to your real Chrome, preserving logins, or to a Browser Use cloud browser.
## Install
```text theme={null}
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.
```
## Links
# cloud
Source: https://docs.browser-use.com/open-source/examples/skills/cloud
Documentation reference for using Browser Use Cloud — the hosted API and SDK for browser automation.
The `cloud` skill gives your agent a reference for [Browser Use Cloud](/cloud/quickstart): the REST API (v2 and v3), the `browser-use-sdk` (Python and TypeScript), `X-Browser-Use-API-Key` authentication, cloud sessions, browser profiles and profile sync, CDP WebSocket access, stealth browsers, residential proxies, CAPTCHA handling, webhooks, workspaces, the skills marketplace, `liveUrl` streaming, pricing, and integration patterns (chat UI, subagents, n8n/Make/Zapier, Playwright/Puppeteer/Selenium).
For the open-source Python library (`Agent`, `Browser`, `Tools`), use the [`open-source`](/open-source/examples/skills/open-source) skill instead.
## Install
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill cloud
```
## Links
# open-source
Source: https://docs.browser-use.com/open-source/examples/skills/open-source
Documentation reference for writing Python code using the browser-use open-source library.
The `open-source` skill gives your agent a reference for writing Python against the [`browser-use` library](/open-source/quickstart): `Agent`, `Browser`, and `Tools` configuration, supported LLM models (15+ providers), the Actor API, custom tools, lifecycle hooks, MCP server setup, sensitive-data handling, prompting strategies, and monitoring/observability with Laminar or OpenLIT.
For the Cloud API/SDK, use the [`cloud`](/open-source/examples/skills/cloud) skill. To drive a browser directly via CLI, use the [`browser-use`](/open-source/examples/skills/browser-use) skill.
## Install
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill open-source
```
## Links
# Overview
Source: https://docs.browser-use.com/open-source/examples/skills/overview
Install Browser Use as an agent skill. Drop browser automation, cloud, QA, and payments into Claude Code, Cursor, and any agent that supports SKILL.md.
Browser Use ships a set of [agent skills](https://www.skills.sh/browser-use/browser-use) — self-contained `SKILL.md` bundles that teach a coding agent (Claude Code, Cursor, OpenClaw, Hermes, and others) how to use Browser Use. Each skill is installable from [skills.sh](https://www.skills.sh/browser-use/browser-use) with one command.
## Install
Every skill installs through the [skills CLI](https://www.skills.sh) with the same pattern — swap in the skill name with `--skill`:
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill
```
For example, to install the QA skill:
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill qa
```
The skills CLI drops the skill into your agent's skills directory (e.g. `.claude/skills/` for Claude Code). Your agent picks it up automatically — no extra wiring.
## Available skills
Drive a browser directly from the CLI — navigate, click, fill forms, screenshot, and extract data.
Reference for Browser Use Cloud — the hosted REST API (v2 & v3) and the Python/TypeScript SDKs.
Reference for writing Python against the `browser-use` library — Agent, Browser, Tools, and more.
QA-test any site or local dev server and return a 1–5 quality score with evidence.
Control a headless browser from a sandboxed remote machine (cloud VMs, CI, coding agents).
Pay for Browser Use Cloud per request from a crypto wallet — no signup or API key.
## Source
All skills live in the [`skills/` directory](https://github.com/browser-use/browser-use/tree/main/skills) of the `browser-use` repo.
# qa
Source: https://docs.browser-use.com/open-source/examples/skills/qa
QA-test a website or web app and return a 1–5 quality score (5 = flawless, 1 = broken) with evidence.
The `qa` skill drives a website with a real browser, judges how well it does the thing you asked about, and returns a **score from 1 (broken) to 5 (excellent)** with evidence — the deliverable is a verdict, not a screenshot dump. Use it to test, QA, evaluate, or score a site, page, flow, or app, including a local dev server (e.g. `localhost:5173`) which it tunnels out automatically. It runs on a real Browser Use cloud browser.
## Install
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill qa
```
## Links
# remote-browser
Source: https://docs.browser-use.com/open-source/examples/skills/remote-browser
Controls a local browser from a sandboxed remote machine. Use when the agent runs in a sandbox (no GUI) and needs to navigate, interact, screenshot, or expose local dev servers via tunnels.
The `remote-browser` skill is for agents running on **sandboxed remote machines** (cloud VMs, CI, coding agents) that need to control a headless browser. It uses the same [Browser Use CLI](/open-source/browser-use-cli) workflow as the [`browser-use`](/open-source/examples/skills/browser-use) skill — `open`, `state`, `click`, `input`, `screenshot`, `close` — plus headless Chromium, cloud browsers, CDP connection, and tunnels to expose local dev servers.
## Install
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill remote-browser
```
## Links
# x402
Source: https://docs.browser-use.com/open-source/examples/skills/x402
Set up Browser Use Cloud payments with x402 — pay per request from a crypto wallet (USDC on Base mainnet), no signup or API key.
The `x402` skill walks your agent through setting up [Browser Use Cloud payments with x402](/cloud/guides/x402) — pay per request from a crypto wallet (USDC on Base mainnet), with no signup, API key, or credit card. It guides wallet setup, funding, writing the key to `.env`, and a \~\$1 test run. The SDK is the easiest path, and non-SDK flows are available via x402-aware client libraries. Use it when you want pay-per-use access without an API key.
## Install
```bash theme={null}
npx skills add https://github.com/browser-use/browser-use --skill x402
```
## Links
# Fast Agent
Source: https://docs.browser-use.com/open-source/examples/templates/fast-agent
Optimize agent performance for maximum speed and efficiency.
```python theme={null}
import asyncio
from dotenv import load_dotenv
load_dotenv()
from browser_use import Agent, BrowserProfile
# Speed optimization instructions for the model
SPEED_OPTIMIZATION_PROMPT = """
Speed optimization instructions:
- Be extremely concise and direct in your responses
- Get to the goal as quickly as possible
- Use multi-action sequences whenever possible to reduce steps
"""
async def main():
# 1. Use fast LLM - Llama 4 on Groq for ultra-fast inference
from browser_use import ChatGroq
llm = ChatGroq(
model='meta-llama/llama-4-maverick-17b-128e-instruct',
temperature=0.0,
)
# from browser_use import ChatGoogle
# llm = ChatGoogle(model='gemini-flash-lite-latest')
# 2. Create speed-optimized browser profile
browser_profile = BrowserProfile(
minimum_wait_page_load_time=0.1,
wait_between_actions=0.1,
headless=False,
)
# 3. Define a speed-focused task
task = """
1. Go to reddit https://www.reddit.com/search/?q=browser+agent&type=communities
2. Click directly on the first 5 communities to open each in new tabs
3. Find out what the latest post is about, and switch directly to the next tab
4. Return the latest post summary for each page
"""
# 4. Create agent with all speed optimizations
agent = Agent(
task=task,
llm=llm,
flash_mode=True, # Disables thinking in the LLM output for maximum speed
browser_profile=browser_profile,
extend_system_message=SPEED_OPTIMIZATION_PROMPT,
)
await agent.run()
if __name__ == '__main__':
asyncio.run(main())
```
## Speed Optimization Techniques
### 1. Fast LLM Models
```python theme={null}
# Groq - Ultra-fast inference
from browser_use import ChatGroq
llm = ChatGroq(model='meta-llama/llama-4-maverick-17b-128e-instruct')
# Google Gemini Flash - Optimized for speed
from browser_use import ChatGoogle
llm = ChatGoogle(model='gemini-flash-lite-latest')
```
### 2. Browser Optimizations
```python theme={null}
browser_profile = BrowserProfile(
minimum_wait_page_load_time=0.1, # Reduce wait time
wait_between_actions=0.1, # Faster action execution
headless=True, # No GUI overhead
)
```
### 3. Agent Optimizations
```python theme={null}
agent = Agent(
task=task,
llm=llm,
flash_mode=True, # Skip LLM thinking process
extend_system_message=SPEED_PROMPT, # Optimize LLM behavior
)
```
# Follow up tasks
Source: https://docs.browser-use.com/open-source/examples/templates/follow-up-tasks
Follow up tasks with the same browser session.
## Chain Agent Tasks
Keep your browser session alive and chain multiple tasks together. Perfect for conversational workflows or multi-step processes.
```python theme={null}
from dotenv import load_dotenv
from browser_use import Agent, Browser
load_dotenv()
import asyncio
async def main():
browser = Browser(keep_alive=True)
await browser.start()
agent = Agent(task='search for browser-use.', browser_session=browser)
await agent.run(max_steps=2)
agent.add_new_task('return the title of first result')
await agent.run()
await browser.kill()
asyncio.run(main())
```
## How It Works
1. **Persistent Browser**: `Browser(keep_alive=True)` prevents browser from closing between tasks
2. **Task Chaining**: Use `agent.add_new_task()` to add follow-up tasks
3. **Context Preservation**: Agent maintains memory and browser state across tasks
4. **Interactive Flow**: Perfect for conversational interfaces
5. **Break down long flows**: If you have very long flows, you can keep the browser alive and send new agents to it.
The browser session remains active throughout the entire chain, preserving all cookies, local storage, and page state.
# More Examples
Source: https://docs.browser-use.com/open-source/examples/templates/more-examples
Explore additional examples and use cases on GitHub.
### 🔗 Browse All Examples
**[View Complete Examples Directory →](https://github.com/browser-use/browser-use/tree/main/examples)**
### 🤝 Contributing Examples
Have a great use case? **[Submit a pull request](https://github.com/browser-use/browser-use/pulls)** with your example!
# Parallel Agents
Source: https://docs.browser-use.com/open-source/examples/templates/parallel-browser
Run multiple agents in parallel with separate browser instances
```python theme={null}
import asyncio
from browser_use import Agent, Browser, ChatOpenAI
async def main():
# Create 3 separate browser instances
browsers = [
Browser(
user_data_dir=f'./temp-profile-{i}',
headless=False,
)
for i in range(3)
]
# Create 3 agents with different tasks
agents = [
Agent(
task='Search for "browser automation" on Google',
browser=browsers[0],
llm=ChatOpenAI(model='gpt-4.1-mini'),
),
Agent(
task='Search for "AI agents" on DuckDuckGo',
browser=browsers[1],
llm=ChatOpenAI(model='gpt-4.1-mini'),
),
Agent(
task='Visit Wikipedia and search for "web scraping"',
browser=browsers[2],
llm=ChatOpenAI(model='gpt-4.1-mini'),
),
]
# Run all agents in parallel
tasks = [agent.run() for agent in agents]
results = await asyncio.gather(*tasks, return_exceptions=True)
print('🎉 All agents completed!')
```
> **Note:** This is experimental, and agents might conflict each other.
# Playwright Integration
Source: https://docs.browser-use.com/open-source/examples/templates/playwright-integration
Advanced example showing Playwright and Browser-Use working together
## Key Features
1. Browser-Use and Playwright sharing the same Chrome instance via CDP
2. Take actions with Playwright and continue with Browser-Use actions
3. Let the agent call Playwright functions like screenshot or click on selectors for deterministic steps
## Installation
```bash theme={null}
uv pip install playwright aiohttp
```
## Full Example
```python theme={null}
import asyncio
import os
import subprocess
import sys
import tempfile
from pydantic import BaseModel, Field
# Check for required dependencies first - before other imports
try:
import aiohttp # type: ignore
from playwright.async_api import Browser, Page, async_playwright # type: ignore
except ImportError as e:
print(f'❌ Missing dependencies for this example: {e}')
print('This example requires: playwright aiohttp')
print('Install with: uv add playwright aiohttp')
print('Also run: playwright install chromium')
sys.exit(1)
from browser_use import Agent, BrowserSession, ChatOpenAI, Tools
from browser_use.agent.views import ActionResult
# Global Playwright browser instance - shared between custom actions
playwright_browser: Browser | None = None
playwright_page: Page | None = None
# Custom action parameter models
class PlaywrightFillFormAction(BaseModel):
"""Parameters for Playwright form filling action."""
customer_name: str = Field(..., description='Customer name to fill')
phone_number: str = Field(..., description='Phone number to fill')
email: str = Field(..., description='Email address to fill')
size_option: str = Field(..., description='Size option (small/medium/large)')
class PlaywrightScreenshotAction(BaseModel):
"""Parameters for Playwright screenshot action."""
filename: str = Field(default='playwright_screenshot.png', description='Filename for screenshot')
quality: int | None = Field(default=None, description='JPEG quality (1-100), only for .jpg/.jpeg files')
class PlaywrightGetTextAction(BaseModel):
"""Parameters for getting text using Playwright selectors."""
selector: str = Field(..., description='CSS selector to get text from. Use "title" for page title.')
async def start_chrome_with_debug_port(port: int = 9222):
"""
Start Chrome with remote debugging enabled.
Returns the Chrome process.
"""
# Create temporary directory for Chrome user data
user_data_dir = tempfile.mkdtemp(prefix='chrome_cdp_')
# Chrome launch command
chrome_paths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', # macOS
'/usr/bin/google-chrome', # Linux
'/usr/bin/chromium-browser', # Linux Chromium
'chrome', # Windows/PATH
'chromium', # Generic
]
chrome_exe = None
for path in chrome_paths:
if os.path.exists(path) or path in ['chrome', 'chromium']:
try:
# Test if executable works
test_proc = await asyncio.create_subprocess_exec(
path, '--version', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
await test_proc.wait()
chrome_exe = path
break
except Exception:
continue
if not chrome_exe:
raise RuntimeError('❌ Chrome not found. Please install Chrome or Chromium.')
# Chrome command arguments
cmd = [
chrome_exe,
f'--remote-debugging-port={port}',
f'--user-data-dir={user_data_dir}',
'--no-first-run',
'--no-default-browser-check',
'--disable-extensions',
'about:blank', # Start with blank page
]
# Start Chrome process
process = await asyncio.create_subprocess_exec(*cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Wait for Chrome to start and CDP to be ready
cdp_ready = False
for _ in range(20): # 20 second timeout
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f'http://localhost:{port}/json/version', timeout=aiohttp.ClientTimeout(total=1)
) as response:
if response.status == 200:
cdp_ready = True
break
except Exception:
pass
await asyncio.sleep(1)
if not cdp_ready:
process.terminate()
raise RuntimeError('❌ Chrome failed to start with CDP')
return process
playwright_instance = None
async def connect_playwright_to_cdp(cdp_url: str):
"""
Connect Playwright to the same Chrome instance Browser-Use is using.
This enables custom actions to use Playwright functions.
"""
global playwright_browser, playwright_page, playwright_instance
playwright_instance = await async_playwright().start()
playwright_browser = await playwright_instance.chromium.connect_over_cdp(cdp_url)
# Get or create a page
if playwright_browser and playwright_browser.contexts and playwright_browser.contexts[0].pages:
playwright_page = playwright_browser.contexts[0].pages[0]
elif playwright_browser:
context = await playwright_browser.new_context()
playwright_page = await context.new_page()
# Create custom tools that use Playwright functions
tools = Tools()
@tools.registry.action(
"Fill out a form using Playwright's precise form filling capabilities. This uses Playwright selectors for reliable form interaction.",
param_model=PlaywrightFillFormAction,
)
async def playwright_fill_form(params: PlaywrightFillFormAction, browser_session: BrowserSession):
"""
Custom action that uses Playwright to fill forms with high precision.
This demonstrates how to create Browser-Use actions that leverage Playwright's capabilities.
"""
try:
if not playwright_page:
return ActionResult(error='Playwright not connected. Run setup first.')
# Filling form with Playwright's precise selectors
# Wait for form to be ready and fill basic fields
await playwright_page.wait_for_selector('input[name="custname"]', timeout=10000)
await playwright_page.fill('input[name="custname"]', params.customer_name)
await playwright_page.fill('input[name="custtel"]', params.phone_number)
await playwright_page.fill('input[name="custemail"]', params.email)
# Handle size selection - check if it's a select dropdown or radio buttons
size_select = playwright_page.locator('select[name="size"]')
size_radio = playwright_page.locator(f'input[name="size"][value="{params.size_option}"]')
if await size_select.count() > 0:
# It's a select dropdown
await playwright_page.select_option('select[name="size"]', params.size_option)
elif await size_radio.count() > 0:
# It's radio buttons
await playwright_page.check(f'input[name="size"][value="{params.size_option}"]')
else:
raise ValueError(f'Could not find size input field for value: {params.size_option}')
# Get form data to verify it was filled
form_data = {}
form_data['name'] = await playwright_page.input_value('input[name="custname"]')
form_data['phone'] = await playwright_page.input_value('input[name="custtel"]')
form_data['email'] = await playwright_page.input_value('input[name="custemail"]')
# Get size value based on input type
if await size_select.count() > 0:
form_data['size'] = await playwright_page.input_value('select[name="size"]')
else:
# For radio buttons, find the checked one
checked_radio = playwright_page.locator('input[name="size"]:checked')
if await checked_radio.count() > 0:
form_data['size'] = await checked_radio.get_attribute('value')
else:
form_data['size'] = 'none selected'
success_msg = f'✅ Form filled successfully with Playwright: {form_data}'
return ActionResult(
extracted_content=success_msg, include_in_memory=True, long_term_memory=f'Filled form with: {form_data}'
)
except Exception as e:
error_msg = f'❌ Playwright form filling failed: {str(e)}'
return ActionResult(error=error_msg)
@tools.registry.action(
"Take a screenshot using Playwright's screenshot capabilities with high quality and precision.",
param_model=PlaywrightScreenshotAction,
)
async def playwright_screenshot(params: PlaywrightScreenshotAction, browser_session: BrowserSession):
"""
Custom action that uses Playwright's advanced screenshot features.
"""
try:
if not playwright_page:
return ActionResult(error='Playwright not connected. Run setup first.')
# Taking screenshot with Playwright
# Use Playwright's screenshot with full page capture
screenshot_kwargs = {'path': params.filename, 'full_page': True}
# Add quality parameter only for JPEG files
if params.quality is not None and params.filename.lower().endswith(('.jpg', '.jpeg')):
screenshot_kwargs['quality'] = params.quality
await playwright_page.screenshot(**screenshot_kwargs)
success_msg = f'✅ Screenshot saved as {params.filename} using Playwright'
return ActionResult(
extracted_content=success_msg, include_in_memory=True, long_term_memory=f'Screenshot saved: {params.filename}'
)
except Exception as e:
error_msg = f'❌ Playwright screenshot failed: {str(e)}'
return ActionResult(error=error_msg)
@tools.registry.action(
"Extract text from elements using Playwright's powerful CSS selectors and XPath support.", param_model=PlaywrightGetTextAction
)
async def playwright_get_text(params: PlaywrightGetTextAction, browser_session: BrowserSession):
"""
Custom action that uses Playwright's advanced text extraction with CSS selectors and XPath.
"""
try:
if not playwright_page:
return ActionResult(error='Playwright not connected. Run setup first.')
# Extracting text with Playwright selectors
# Handle special selectors
if params.selector.lower() == 'title':
# Use page.title() for title element
text_content = await playwright_page.title()
result_data = {
'selector': 'title',
'text_content': text_content,
'inner_text': text_content,
'tag_name': 'TITLE',
'is_visible': True,
}
else:
# Use Playwright's robust element selection and text extraction
element = playwright_page.locator(params.selector).first
if await element.count() == 0:
error_msg = f'❌ No element found with selector: {params.selector}'
return ActionResult(error=error_msg)
text_content = await element.text_content()
inner_text = await element.inner_text()
# Get additional element info
tag_name = await element.evaluate('el => el.tagName')
is_visible = await element.is_visible()
result_data = {
'selector': params.selector,
'text_content': text_content,
'inner_text': inner_text,
'tag_name': tag_name,
'is_visible': is_visible,
}
success_msg = f'✅ Extracted text using Playwright: {result_data}'
return ActionResult(
extracted_content=str(result_data),
include_in_memory=True,
long_term_memory=f'Extracted from {params.selector}: {result_data["text_content"]}',
)
except Exception as e:
error_msg = f'❌ Playwright text extraction failed: {str(e)}'
return ActionResult(error=error_msg)
async def main():
"""
Main function demonstrating Browser-Use + Playwright integration with custom actions.
"""
print('🚀 Advanced Playwright + Browser-Use Integration with Custom Actions')
chrome_process = None
try:
# Step 1: Start Chrome with CDP debugging
chrome_process = await start_chrome_with_debug_port()
cdp_url = 'http://localhost:9222'
# Step 2: Connect Playwright to the same Chrome instance
await connect_playwright_to_cdp(cdp_url)
# Step 3: Create Browser-Use session connected to same Chrome
browser_session = BrowserSession(cdp_url=cdp_url)
# Step 4: Create AI agent with our custom Playwright-powered tools
agent = Agent(
task="""
Please help me demonstrate the integration between Browser-Use and Playwright:
1. First, navigate to https://httpbin.org/forms/post
2. Use the 'playwright_fill_form' action to fill the form with these details:
- Customer name: "Alice Johnson"
- Phone: "555-9876"
- Email: "alice@demo.com"
- Size: "large"
3. Take a screenshot using the 'playwright_screenshot' action and save it as "form_demo.png"
4. Extract the title of the page using 'playwright_get_text' action with selector "title"
5. Finally, submit the form and tell me what happened
This demonstrates how Browser-Use AI can orchestrate tasks while using Playwright's precise capabilities for specific operations.
""",
llm=ChatOpenAI(model='gpt-4.1-mini'),
tools=tools, # Our custom tools with Playwright actions
browser_session=browser_session,
)
print('🎯 Starting AI agent with custom Playwright actions...')
# Step 5: Run the agent - it will use both Browser-Use actions and our custom Playwright actions
result = await agent.run()
# Keep browser open briefly to see results
print(f'✅ Integration demo completed! Result: {result}')
await asyncio.sleep(2) # Brief pause to see results
except Exception as e:
print(f'❌ Error: {e}')
raise
finally:
# Clean up resources
if playwright_browser:
await playwright_browser.close()
if playwright_instance:
await playwright_instance.stop()
if chrome_process:
chrome_process.terminate()
try:
await asyncio.wait_for(chrome_process.wait(), 5)
except TimeoutError:
chrome_process.kill()
print('✅ Cleanup complete')
if __name__ == '__main__':
# Run the advanced integration demo
asyncio.run(main())
```
# Secure Setup
Source: https://docs.browser-use.com/open-source/examples/templates/secure
Azure OpenAI with data privacy and security configuration.
## Secure Setup with Azure OpenAI
Enterprise-grade security with Azure OpenAI, data privacy protection, and restricted browser access.
```python theme={null}
import asyncio
import os
from dotenv import load_dotenv
load_dotenv()
os.environ['ANONYMIZED_TELEMETRY'] = 'false'
from browser_use import Agent, BrowserProfile, ChatAzureOpenAI
# Azure OpenAI configuration
api_key = os.getenv('AZURE_OPENAI_KEY')
azure_endpoint = os.getenv('AZURE_OPENAI_ENDPOINT')
llm = ChatAzureOpenAI(model='gpt-4.1-mini', api_key=api_key, azure_endpoint=azure_endpoint)
# Secure browser configuration
browser_profile = BrowserProfile(
allowed_domains=['*google.com', 'browser-use.com'],
enable_default_extensions=False
)
# Sensitive data filtering
sensitive_data = {'company_name': 'browser-use'}
# Create secure agent
agent = Agent(
task='Find the founders of the sensitive company_name',
llm=llm,
browser_profile=browser_profile,
sensitive_data=sensitive_data
)
async def main():
await agent.run(max_steps=10)
asyncio.run(main())
```
## Security Features
**Azure OpenAI:**
* NOT used to train OpenAI models
* NOT shared with other customers
* Hosted entirely within Azure
* 30-day retention (or zero with Limited Access Program)
**Browser Security:**
* `allowed_domains`: Restrict navigation to trusted sites
* `enable_default_extensions=False`: Disable potentially dangerous extensions
* `sensitive_data`: Filter sensitive information from LLM input
For enterprise deployments contact [support@browser-use.com](mailto:support@browser-use.com).
# Sensitive Data
Source: https://docs.browser-use.com/open-source/examples/templates/sensitive-data
Handle secret information securely and avoid sending PII & passwords to the LLM.
For comprehensive authentication guidance including real browser profiles, storage state, and 2FA, see the [Authentication Guide](/open-source/customize/browser/authentication).
```python theme={null}
import os
from browser_use import Agent, Browser, ChatOpenAI
os.environ['ANONYMIZED_TELEMETRY'] = "false"
company_credentials = {'x_user': 'your-real-username@email.com', 'x_pass': 'your-real-password123'}
# Option 1: Secrets available for all websites
sensitive_data = company_credentials
# Option 2: Secrets per domain with regex
# sensitive_data = {
# 'https://*.example-staging.com': company_credentials,
# 'http*://test.example.com': company_credentials,
# 'https://example.com': company_credentials,
# 'https://google.com': {'g_email': 'user@gmail.com', 'g_pass': 'google_password'},
# }
agent = Agent(
task='Log into example.com with username x_user and password x_pass',
sensitive_data=sensitive_data,
use_vision=False, # Disable vision to prevent LLM seeing sensitive data in screenshots
llm=ChatOpenAI(model='gpt-4.1-mini'),
)
async def main():
await agent.run()
```
## How it Works
1. **Text Filtering**: The LLM only sees placeholders (`x_user`, `x_pass`), we filter your sensitive data from the input text.
2. **DOM Actions**: Real values are injected directly into form fields after the LLM call
## Best Practices
* Use `Browser(allowed_domains=[...])` to restrict navigation
* Set `use_vision=False` to prevent screenshot leaks
* Use `storage_state='./auth.json'` for login cookies instead of passwords when possible
# Browser Use Open Source
Source: https://docs.browser-use.com/open-source/introduction
Python library for AI browser automation with 79k+ GitHub stars. Connect any LLM and run locally or self-hosted.
Install and run your first agent.
Full SDK reference in a single page.
Try [Browser Use Cloud](/cloud/introduction) for our SOTA model, stealth browsers, CAPTCHA solving, and managed infrastructure.
# All Parameters
Source: https://docs.browser-use.com/open-source/legacy/actor/all-parameters
Complete API reference for Browser Actor classes, methods, and parameters including BrowserSession, Page, Element, and Mouse
## Browser (BrowserSession)
Main browser session manager.
### Key Methods
```python theme={null}
from browser_use import Browser
browser = Browser()
await browser.start()
# Page management
page = await browser.new_page("https://example.com")
pages = await browser.get_pages()
current = await browser.get_current_page()
await browser.close_page(page)
# To stop the browser session
await browser.stop()
```
### Constructor Parameters
See [Browser Parameters](/open-source/customize/browser/all-parameters) for complete configuration options.
## Page
Browser tab/iframe for page-level operations.
### Navigation
* `goto(url: str)` - Navigate to URL
* `go_back()`, `go_forward()`, `reload()` - History navigation
### Element Finding
* `get_elements_by_css_selector(selector: str) -> list[Element]` - CSS selector
* `get_element(backend_node_id: int) -> Element` - By CDP node ID
* `get_element_by_prompt(prompt: str, llm) -> Element | None` - AI-powered
* `must_get_element_by_prompt(prompt: str, llm) -> Element` - AI (raises if not found)
### JavaScript & Controls
* `evaluate(page_function: str, *args) -> str` - Execute JS (arrow function format)
* `press(key: str)` - Send keyboard input ("Enter", "Control+A")
* `set_viewport_size(width: int, height: int)` - Set viewport
* `screenshot(format='jpeg', quality=None) -> str` - Take screenshot
### Information
* `get_url() -> str`, `get_title() -> str` - Page info
* `mouse -> Mouse` - Get mouse interface
### AI Features
* `extract_content(prompt: str, structured_output: type[T], llm) -> T` - Extract data
## Element
Individual DOM element interactions.
### Interactions
* `click(button='left', click_count=1, modifiers=None)` - Click element
* `fill(text: str, clear=True)` - Fill input
* `hover()`, `focus()` - Mouse/focus actions
* `check()` - Toggle checkbox/radio
* `select_option(values: str | list[str])` - Select dropdown options
* `drag_to(target: Element | Position)` - Drag and drop
### Properties
* `get_attribute(name: str) -> str | None` - Get attribute
* `get_bounding_box() -> BoundingBox | None` - Position/size
* `get_basic_info() -> ElementInfo` - Complete element info
* `screenshot(format='jpeg') -> str` - Element screenshot
## Mouse
Coordinate-based mouse operations.
### Operations
* `click(x: int, y: int, button='left', click_count=1)` - Click at coordinates
* `move(x: int, y: int, steps=1)` - Move mouse
* `down(button='left')`, `up(button='left')` - Press/release buttons
* `scroll(x=0, y=0, delta_x=None, delta_y=None)` - Scroll at coordinates
# Basics
Source: https://docs.browser-use.com/open-source/legacy/actor/basics
Low-level Playwright-like browser automation with direct and full CDP control and precise element interactions
## Core Architecture
```mermaid theme={null}
graph TD
A[Browser] --> B[Page]
B --> C[Element]
B --> D[Mouse]
B --> E[AI Features]
C --> F[DOM Interactions]
D --> G[Coordinate Operations]
E --> H[LLM Integration]
```
### Core Classes
* **Browser** (alias: **BrowserSession**): Main session manager
* **Page**: Represents a browser tab/iframe
* **Element**: Individual DOM element operations
* **Mouse**: Coordinate-based mouse operations
## Basic Usage
```python theme={null}
from browser_use import Browser, Agent
from browser_use.llm.openai.chat import ChatOpenAI
async def main():
llm = ChatOpenAI(api_key="your-api-key")
browser = Browser()
await browser.start()
# 1. Actor: Precise navigation and element interactions
page = await browser.new_page("https://github.com/login")
email_input = await page.must_get_element_by_prompt("username field", llm=llm)
await email_input.fill("your-username")
# 2. Agent: AI-driven complex tasks
agent = Agent(browser=browser, llm=llm)
await agent.run("Complete login and navigate to my repositories")
await browser.stop()
```
## Important Notes
* **Not Playwright**: Actor is built on CDP, not Playwright. The API resembles Playwright as much as possible for easy migration, but is a subset.
* **Immediate Returns**: `get_elements_by_css_selector()` doesn't wait for visibility
* **Manual Timing**: You handle navigation timing and waiting
* **JavaScript Format**: `evaluate()` requires arrow function format: `() => {}`
# Examples
Source: https://docs.browser-use.com/open-source/legacy/actor/examples
Comprehensive examples for Browser Actor automation tasks including forms, JavaScript, mouse operations, and AI features
## Page Management
```python theme={null}
from browser_use import Browser
browser = Browser()
await browser.start()
# Create pages
page = await browser.new_page() # Blank tab
page = await browser.new_page("https://example.com") # With URL
# Get all pages
pages = await browser.get_pages()
current = await browser.get_current_page()
# Close page
await browser.close_page(page)
await browser.stop()
```
## Element Finding & Interactions
```python theme={null}
page = await browser.new_page('https://github.com')
# CSS selectors (immediate return)
elements = await page.get_elements_by_css_selector("input[type='text']")
buttons = await page.get_elements_by_css_selector("button.submit")
# Element actions
await elements[0].click()
await elements[0].fill("Hello World")
await elements[0].hover()
# Page actions
await page.press("Enter")
screenshot = await page.screenshot()
```
## LLM-Powered Features
```python theme={null}
from browser_use.llm.openai.chat import ChatOpenAI
from pydantic import BaseModel
llm = ChatOpenAI(api_key="your-api-key")
# Find elements using natural language
button = await page.get_element_by_prompt("login button", llm=llm)
await button.click()
# Extract structured data
class ProductInfo(BaseModel):
name: str
price: float
product = await page.extract_content(
"Extract product name and price",
ProductInfo,
llm=llm
)
```
## JavaScript Execution
```python theme={null}
# Simple JavaScript evaluation
title = await page.evaluate('() => document.title')
# JavaScript with arguments
result = await page.evaluate('(x, y) => x + y', 10, 20)
# Complex operations
stats = await page.evaluate('''() => ({
url: location.href,
links: document.querySelectorAll('a').length
})''')
```
## Mouse Operations
```python theme={null}
mouse = page.mouse
# Click at coordinates
await mouse.click(x=100, y=200)
# Drag and drop
await mouse.down()
await mouse.move(x=500, y=600)
await mouse.up()
# Scroll
await mouse.scroll(x=0, y=100, delta_y=-500)
```
## Best Practices
* Use `asyncio.sleep()` after actions that trigger navigation
* Check URL/title changes to verify state transitions
* Always check if elements exist before interaction
* Implement retry logic for flaky elements
* Call `browser.stop()` to clean up resources
# All Parameters
Source: https://docs.browser-use.com/open-source/legacy/sandbox/all-parameters
Sandbox configuration reference
## Reference
| Parameter | Type | Description | Default |
| -------------------------- | ---------- | -------------------------------------- | -------- |
| `BROWSER_USE_API_KEY` | `str` | API key (or env var) | Required |
| `cloud_profile_id` | `str` | Browser profile UUID | `None` |
| `cloud_proxy_country_code` | `str` | us, uk, fr, it, jp, au, de, fi, ca, in | `None` |
| `cloud_timeout` | `int` | Minutes (max: 15 free, 240 paid) | `None` |
| `on_browser_created` | `Callable` | Live URL callback | `None` |
| `on_log` | `Callable` | Log event callback | `None` |
| `on_result` | `Callable` | Success callback | `None` |
| `on_error` | `Callable` | Error callback | `None` |
## Example
```python theme={null}
@sandbox(
cloud_profile_id='550e8400-e29b-41d4-a716-446655440000',
cloud_proxy_country_code='us',
cloud_timeout=60,
on_browser_created=lambda data: print(f'Live: {data.live_url}'),
)
async def task(browser: Browser):
agent = Agent(task="your task", browser=browser, llm=ChatBrowserUse())
await agent.run()
```
# Events
Source: https://docs.browser-use.com/open-source/legacy/sandbox/events
Monitor execution with callbacks
## Live Browser View
```python theme={null}
@sandbox(on_browser_created=lambda data: print(f'👁️ {data.live_url}'))
async def task(browser: Browser):
agent = Agent(task="your task", browser=browser, llm=ChatBrowserUse())
await agent.run()
```
## All Events
```python theme={null}
from browser_use.sandbox import BrowserCreatedData, LogData, ResultData, ErrorData
@sandbox(
on_browser_created=lambda data: print(f'Live: {data.live_url}'),
on_log=lambda log: print(f'{log.level}: {log.message}'),
on_result=lambda result: print('Done!'),
on_error=lambda error: print(f'Error: {error.error}'),
)
async def task(browser: Browser):
# Your code
pass
```
All callbacks can be sync or async.
# Quickstart
Source: https://docs.browser-use.com/open-source/legacy/sandbox/quickstart
Run browser automation in the cloud
Sandboxes are the **easiest way to run Browser-Use in production**. We handle agents, browsers, persistence, auth, cookies, and LLMs. It is also the **fastest way to deploy** - the agent runs right next to the browser, so latency is minimal.
Get your API key at [cloud.browser-use.com/new-api-key](https://cloud.browser-use.com/new-api-key) — eligible new accounts receive \$15 in one-time credits.
## Basic Example
Wrap your function with `@sandbox()`:
```python theme={null}
from browser_use import Browser, sandbox, ChatBrowserUse
from browser_use.agent.service import Agent
@sandbox()
async def my_task(browser: Browser):
agent = Agent(task="Find the top HN post", browser=browser, llm=ChatBrowserUse())
await agent.run()
await my_task()
```
## With Cloud Parameters
```python theme={null}
@sandbox(
cloud_profile_id='your-profile-id', # Use saved cookies/auth
cloud_proxy_country_code='us', # Bypass captchas, cloudflare, geo-restrictions
cloud_timeout=60, # Max session time (minutes)
)
async def task(browser: Browser, url: str):
agent = Agent(task=f"Visit {url}", browser=browser, llm=ChatBrowserUse())
await agent.run()
await task(url="https://example.com")
```
**What each does:**
* `cloud_profile_id` - Use saved cookies/authentication from your cloud profile
* `cloud_proxy_country_code` - Route through country-specific proxy for stealth (bypass captchas, Cloudflare, geo-blocks)
* `cloud_timeout` - Maximum time browser stays open in minutes
***
For more parameters and events, see the other tabs in this section.
# Human Quickstart
Source: https://docs.browser-use.com/open-source/quickstart
Install and run your first AI browser agent in minutes. Python setup with pip, environment variables, and a working example.
To get started with Browser Use you need to install the package and create an `.env` file with your API key.
`ChatBrowserUse` offers the [fastest and most cost-effective models](https://browser-use.com/posts/speed-matters/), completing tasks 3-5x faster. Eligible new accounts receive \$15 in one-time credits. Get your API key [here](https://cloud.browser-use.com/new-api-key).
## 1. Installing Browser-Use
```bash create environment theme={null}
pip install uv
uv venv --python 3.12
```
```bash activate environment theme={null}
source .venv/bin/activate
# On Windows use `.venv\Scripts\activate`
```
```bash install browser-use & chromium theme={null}
uv pip install browser-use
uvx browser-use install
```
## 2. Choose your favorite LLM
Create a `.env` file and add your API key.
We recommend using ChatBrowserUse which is optimized for browser automation tasks (highest accuracy + fastest speed + lowest token cost). Get your API key [here](https://cloud.browser-use.com/new-api-key) — eligible new accounts receive \$15 in one-time credits.
```bash .env theme={null}
touch .env
```
On Windows, use `echo. > .env`
Then add your API key to the file.
```bash Browser Use theme={null}
# add your key to .env file
BROWSER_USE_API_KEY=
# Get your API key at https://cloud.browser-use.com/new-api-key - eligible new accounts receive $15 in one-time credits
```
```bash Google theme={null}
# add your key to .env file
GOOGLE_API_KEY=
# Get your free Gemini API key from https://aistudio.google.com/app/u/1/apikey?pli=1.
```
```bash OpenAI theme={null}
# add your key to .env file
OPENAI_API_KEY=
```
```bash Anthropic theme={null}
# add your key to .env file
ANTHROPIC_API_KEY=
```
See [Supported Models](/open-source/supported-models) for more.
## 3. Run your first agent
```python Browser Use theme={null}
from browser_use import Agent, ChatBrowserUse
from dotenv import load_dotenv
import asyncio
load_dotenv()
async def main():
llm = ChatBrowserUse()
task = "Find the number 1 post on Show HN"
agent = Agent(task=task, llm=llm)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
```python Google theme={null}
from browser_use import Agent, ChatGoogle
from dotenv import load_dotenv
import asyncio
load_dotenv()
async def main():
llm = ChatGoogle(model="gemini-flash-latest")
task = "Find the number 1 post on Show HN"
agent = Agent(task=task, llm=llm)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
```python OpenAI theme={null}
from browser_use import Agent, ChatOpenAI
from dotenv import load_dotenv
import asyncio
load_dotenv()
async def main():
llm = ChatOpenAI(model="gpt-4.1-mini")
task = "Find the number 1 post on Show HN"
agent = Agent(task=task, llm=llm)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
```python Anthropic theme={null}
from browser_use import Agent, ChatAnthropic
from dotenv import load_dotenv
import asyncio
load_dotenv()
async def main():
llm = ChatAnthropic(model='claude-sonnet-4-0', temperature=0.0)
task = "Find the number 1 post on Show HN"
agent = Agent(task=task, llm=llm)
await agent.run()
if __name__ == "__main__":
asyncio.run(main())
```
Custom browsers can be configured in one line. Check out browsers for more.
## 4. Going to Production
Sandboxes are the **easiest way to run Browser-Use in production**. We handle agents, browsers, persistence, auth, cookies, and LLMs. It is also the **fastest way to deploy** - the agent runs right next to the browser, so latency is minimal.
To run in production with authentication, add `@sandbox` to your function:
```python theme={null}
import asyncio
from browser_use import Browser, sandbox, ChatBrowserUse
from browser_use.agent.service import Agent
@sandbox(cloud_profile_id='your-profile-id')
async def production_task(browser: Browser):
agent = Agent(
task="Your authenticated task",
browser=browser,
llm=ChatBrowserUse(),
)
await agent.run()
if __name__ == "__main__":
asyncio.run(production_task())
```
See [Browser Use Cloud](/cloud/quickstart) for how to sync your cookies to the cloud.
# Supported Models
Source: https://docs.browser-use.com/open-source/supported-models
Choose between Claude, Gemini, Llama, DeepSeek, and more. Configuration examples for each provider.
Browser Use natively supports 15+ LLM providers. Most providers accept any model string. Check each provider's docs to see which models are available.
> **Which model should I use?** See our [benchmark results and recommendations](https://browser-use.com/posts/what-model-to-use) for detailed comparisons across real-world browser tasks.
### Browser Use [example](https://github.com/browser-use/browser-use/blob/main/examples/models/browser_use_llm.py)
`ChatBrowserUse()` is our optimized in-house model, matching the accuracy of top models while completing tasks **3-5x** faster. [See our blog post→](https://browser-use.com/posts/speed-matters)
Read the [bu-2-0 model card](/open-source/bu-2-0-model-card) for details on intended use, inputs and outputs, tools, benchmarks, judge setup, and limitations.
```python theme={null}
from browser_use import Agent, ChatBrowserUse
# Initialize the model - defaults to bu-latest (bu-2-0)
llm = ChatBrowserUse()
# Create agent with the model
agent = Agent(
task="...", # Your task here
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
BROWSER_USE_API_KEY=
```
Get your API key from the [Browser Use Cloud](https://cloud.browser-use.com/new-api-key). Eligible new accounts receive \$15 in one-time credits.
#### Pricing
ChatBrowserUse offers competitive pricing per 1 million tokens:
**bu-2-0 / bu-latest (Default)**
| Token Type | Price per 1M tokens |
| ------------- | ------------------- |
| Input tokens | \$0.60 |
| Cached tokens | \$0.06 |
| Output tokens | \$3.50 |
**bu-1-0**
Since May 20, 2026, `bu-1-0` is priced the same as `bu-2-0`:
| Token Type | Price per 1M tokens |
| ------------- | ------------------- |
| Input tokens | \$0.60 |
| Cached tokens | \$0.06 |
| Output tokens | \$3.50 |
### Google Gemini [example](https://github.com/browser-use/browser-use/blob/main/examples/models/gemini.py)
[Available models](https://ai.google.dev/api/models). Also supports Gemma models and Vertex AI via `ChatGoogle(model="...", vertexai=True)`.
`GEMINI_API_KEY` is deprecated and should be named `GOOGLE_API_KEY` as of 2025-05.
```python theme={null}
from browser_use import Agent, ChatGoogle
from dotenv import load_dotenv
# Read GOOGLE_API_KEY into env
load_dotenv()
# Initialize the model
llm = ChatGoogle(model='gemini-2.5-flash')
# Create agent with the model
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
GOOGLE_API_KEY=
```
### OpenAI [example](https://github.com/browser-use/browser-use/blob/main/examples/models/gpt-5-mini.py)
[Available models](https://platform.openai.com/docs/models)
```python theme={null}
from browser_use import Agent, ChatOpenAI
# Initialize the model
llm = ChatOpenAI(
model="gpt-5",
)
# Create agent with the model
agent = Agent(
task="...", # Your task here
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
OPENAI_API_KEY=
```
You can use any OpenAI compatible model by passing the model name to the `ChatOpenAI` class using a custom URL (or any other parameter that would go into the normal OpenAI API call).
### Anthropic [example](https://github.com/browser-use/browser-use/blob/main/examples/models/claude-4-sonnet.py)
[Available models](https://docs.anthropic.com/en/docs/about-claude/models). Coordinate clicking is automatically enabled for `claude-sonnet-4-*` and `claude-opus-4-*` models.
```python theme={null}
from browser_use import Agent, ChatAnthropic
# Initialize the model
llm = ChatAnthropic(
model="claude-sonnet-4-6",
)
# Create agent with the model
agent = Agent(
task="...", # Your task here
llm=llm
)
```
And add the variable:
```bash .env theme={null}
ANTHROPIC_API_KEY=
```
### Azure OpenAI [example](https://github.com/browser-use/browser-use/blob/main/examples/models/azure_openai.py)
[Available models](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/models-sold-directly-by-azure)
```python theme={null}
from browser_use import Agent, ChatAzureOpenAI
from pydantic import SecretStr
import os
# Initialize the model
llm = ChatAzureOpenAI(
model="o4-mini",
)
# Create agent with the model
agent = Agent(
task="...", # Your task here
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/
AZURE_OPENAI_API_KEY=
```
#### Using the Responses API (for GPT-5.1 Codex models)
Azure OpenAI now requires `api_version >= 2025-03-01-preview` for certain models like `gpt-5.1-codex-mini`. These models only support the [Responses API](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/responses) instead of the Chat Completions API.
Browser Use automatically detects and uses the Responses API for these models:
* `gpt-5.1-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex-max`
* `gpt-5-codex`, `codex-mini-latest`
* `computer-use-preview`
```python theme={null}
from browser_use import Agent, ChatAzureOpenAI
# Auto-detection (recommended) - uses Responses API for gpt-5.1-codex-mini
llm = ChatAzureOpenAI(
model="gpt-5.1-codex-mini",
api_version="2025-03-01-preview", # Required for Responses API
)
# Or explicitly enable/disable Responses API for any model
llm = ChatAzureOpenAI(
model="gpt-4o",
api_version="2025-03-01-preview",
use_responses_api=True, # Force Responses API (True/False/'auto')
)
agent = Agent(
task="...",
llm=llm
)
```
The `use_responses_api` parameter accepts:
* `'auto'` (default): Automatically uses Responses API for models that require it
* `True`: Force use of the Responses API
* `False`: Force use of the Chat Completions API
### AWS Bedrock [example](https://github.com/browser-use/browser-use/blob/main/examples/models/aws.py)
[Available models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html). AWS Bedrock provides access to multiple model providers through a single API. We support both a general AWS Bedrock client and provider-specific convenience classes. Install with `pip install "browser-use[aws]"`.
#### General AWS Bedrock (supports all providers)
```python theme={null}
from browser_use import Agent
from browser_use.llm import ChatAWSBedrock
# Works with any Bedrock model (Anthropic, Meta, AI21, etc.)
llm = ChatAWSBedrock(
model="anthropic.claude-3-5-sonnet-20240620-v1:0", # or any Bedrock model
aws_region="us-east-1",
)
# Create agent with the model
agent = Agent(
task="Your task here",
llm=llm
)
```
#### Anthropic Claude via AWS Bedrock (convenience class)
```python theme={null}
from browser_use import Agent
from browser_use.llm import ChatAnthropicBedrock
# Anthropic-specific class with Claude defaults
llm = ChatAnthropicBedrock(
model="anthropic.claude-3-5-sonnet-20240620-v1:0",
aws_region="us-east-1",
)
# Create agent with the model
agent = Agent(
task="Your task here",
llm=llm
)
```
#### AWS Authentication
Required environment variables:
```bash .env theme={null}
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
```
You can also use AWS profiles or IAM roles instead of environment variables. The implementation supports:
* Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`)
* AWS profiles and credential files
* IAM roles (when running on EC2)
* Session tokens for temporary credentials
* AWS SSO authentication (`aws_sso_auth=True`)
## Vercel AI Gateway [example](https://github.com/browser-use/browser-use/blob/main/examples/models/vercel_ai_gateway.py)
[Available models](https://vercel.com/ai-gateway/models). Vercel AI Gateway provides an OpenAI-compatible API endpoint that acts as a proxy to various AI providers, with features like rate limiting, caching, and monitoring.
```python theme={null}
from browser_use import Agent, ChatVercel
from dotenv import load_dotenv
import os
load_dotenv()
# Get API key (https://vercel.com/ai-gateway)
api_key = os.getenv('VERCEL_API_KEY')
if not api_key:
raise ValueError('VERCEL_API_KEY is not set')
# Basic usage
llm = ChatVercel(
model='openai/gpt-4o',
api_key=api_key,
)
# With provider options - control which providers are used and in what order
# This will try Vertex AI first, then fall back to Anthropic if Vertex fails
llm_with_provider_options = ChatVercel(
model='anthropic/claude-sonnet-4',
api_key=api_key,
provider_options={
'gateway': {
'order': ['vertex', 'anthropic'] # Try Vertex AI first, then Anthropic
}
},
)
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
VERCEL_API_KEY=
```
## Groq [example](https://github.com/browser-use/browser-use/blob/main/examples/models/llama4-groq.py)
[Available models](https://console.groq.com/docs/models)
```python theme={null}
from browser_use import Agent, ChatGroq
llm = ChatGroq(model="meta-llama/llama-4-maverick-17b-128e-instruct")
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
GROQ_API_KEY=
```
## Oracle Cloud Infrastructure (OCI) [example](https://github.com/browser-use/browser-use/blob/main/examples/models/oci_models.py)
[Available models](https://docs.oracle.com/en-us/iaas/Content/generative-ai/imported-models.htm). OCI provides access to various generative AI models including Meta Llama, Cohere, and other providers through their Generative AI service. Install with `pip install "browser-use[oci]"`.
```python theme={null}
from browser_use import Agent, ChatOCIRaw
# Initialize the OCI model
llm = ChatOCIRaw(
model_id="ocid1.generativeaimodel.oc1.us-chicago-1.amaaaaaask7dceya...",
service_endpoint="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
compartment_id="ocid1.tenancy.oc1..aaaaaaaayeiis5uk2nuubznrekd...",
provider="meta", # or "cohere"
temperature=0.7,
max_tokens=800,
top_p=0.9,
auth_type="API_KEY",
auth_profile="DEFAULT"
)
# Create agent with the model
agent = Agent(
task="Your task here",
llm=llm
)
```
Required setup:
1. Set up OCI configuration file at `~/.oci/config`
2. Have access to OCI Generative AI models in your tenancy
3. Install the OCI Python SDK: `uv add oci` or `pip install oci`
Authentication methods supported:
* `API_KEY`: Uses API key authentication (default)
* `INSTANCE_PRINCIPAL`: Uses instance principal authentication
* `RESOURCE_PRINCIPAL`: Uses resource principal authentication
## Ollama
[Available models](https://ollama.com/library).
1. Install Ollama: [https://github.com/ollama/ollama](https://github.com/ollama/ollama)
2. Run `ollama serve` to start the server
3. In a new terminal, install the model you want to use: `ollama pull llama3.1:8b` (this has 4.9GB)
```python theme={null}
from browser_use import Agent, ChatOllama
llm = ChatOllama(model="llama3.1:8b")
```
## Langchain
[Example](https://github.com/browser-use/browser-use/blob/main/examples/models/langchain) on how to use Langchain with Browser Use.
## Qwen [example](https://github.com/browser-use/browser-use/blob/main/examples/models/qwen.py)
Currently, only `qwen-vl-max` is recommended for Browser Use. Other Qwen models, including `qwen-max`, have issues with the action schema format. Smaller Qwen models may return incorrect action schema formats (e.g., `actions: [{"navigate": "google.com"}]` instead of `[{"navigate": {"url": "google.com"}}]`). If you want to use other models, add concrete examples of the correct action format to your prompt.
```python theme={null}
from browser_use import Agent, ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
# Get API key from https://modelstudio.console.alibabacloud.com/?tab=playground#/api-key
api_key = os.getenv('ALIBABA_CLOUD')
base_url = 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1'
llm = ChatOpenAI(model='qwen-vl-max', api_key=api_key, base_url=base_url)
agent = Agent(
task="Your task here",
llm=llm,
use_vision=True
)
```
Required environment variables:
```bash .env theme={null}
ALIBABA_CLOUD=
```
## ModelScope [example](https://github.com/browser-use/browser-use/blob/main/examples/models/modelscope_example.py)
```python theme={null}
from browser_use import Agent, ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
# Get API key from https://www.modelscope.cn/docs/model-service/API-Inference/intro
api_key = os.getenv('MODELSCOPE_API_KEY')
base_url = 'https://api-inference.modelscope.cn/v1/'
llm = ChatOpenAI(model='Qwen/Qwen2.5-VL-72B-Instruct', api_key=api_key, base_url=base_url)
agent = Agent(
task="Your task here",
llm=llm,
use_vision=True
)
```
Required environment variables:
```bash .env theme={null}
MODELSCOPE_API_KEY=
```
## DeepSeek [example](https://github.com/browser-use/browser-use/blob/main/examples/models/deepseek-chat.py)
[Available models](https://api-docs.deepseek.com/quick_start/pricing)
```python theme={null}
from browser_use import Agent, ChatDeepSeek
llm = ChatDeepSeek(model="deepseek-chat")
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
DEEPSEEK_API_KEY=
```
## Mistral [example](https://github.com/browser-use/browser-use/blob/main/examples/models/mistral.py)
[Available models](https://docs.mistral.ai/getting-started/models/models_overview/)
```python theme={null}
from browser_use import Agent, ChatMistral
llm = ChatMistral(model="mistral-large-latest")
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
MISTRAL_API_KEY=
```
## Cerebras [example](https://github.com/browser-use/browser-use/blob/main/examples/models/cerebras_example.py)
[Available models](https://inference-docs.cerebras.ai/models/overview)
```python theme={null}
from browser_use import Agent, ChatCerebras
llm = ChatCerebras(model="llama3.3-70b")
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
CEREBRAS_API_KEY=
```
## OpenRouter [example](https://github.com/browser-use/browser-use/blob/main/examples/models/openrouter.py)
[Available models](https://openrouter.ai/models). Access 300+ models from any provider through a single API.
```python theme={null}
from browser_use import Agent, ChatOpenRouter
llm = ChatOpenRouter(model="anthropic/claude-sonnet-4-6")
agent = Agent(
task="Your task here",
llm=llm
)
```
Required environment variables:
```bash .env theme={null}
OPENROUTER_API_KEY=
```
## LiteLLM
Requires separate install (`pip install litellm`). Supports any [LiteLLM model string](https://docs.litellm.ai/docs/providers) — useful when you need a provider not covered by the native integrations above.
```python theme={null}
from browser_use import Agent
from browser_use.llm.litellm import ChatLiteLLM
llm = ChatLiteLLM(model="openai/gpt-5")
agent = Agent(
task="Your task here",
llm=llm
)
```
## Other OpenAI-Compatible Providers
Any provider with an OpenAI-compatible endpoint works via `ChatOpenAI` with a custom `base_url`:
**Examples available:**
* [Novita](https://github.com/browser-use/browser-use/blob/main/examples/models/novita.py)
# Prompts for Vibecoders
Source: https://docs.browser-use.com/open-source/vibecoding
Complete Python SDK reference for AI coding agents. Paste into Claude Code, Cursor, or Copilot for instant integration.
1. Copy all content [🔗 from here](https://github.com/browser-use/browser-use/blob/main/AGENTS.md) (\~9k tokens)
2. Paste it into your project
3. Prompt your coding agent (Cursor, Claude, etc.) "Help me get started with Browser Use"