+
+
+Full methodology in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark).
+
+## Supported CAPTCHA types
+
+These are the interactive CAPTCHA widgets solved automatically, distinct from the anti-bot systems above (Cloudflare, DataDome, PerimeterX, Akamai) that gate a site before a widget ever appears:
+
+| CAPTCHA type | Solved automatically |
+| --- | --- |
+| reCAPTCHA v2 (checkbox / image challenge) | Yes |
+| reCAPTCHA v3 (score-based) | Yes |
+| hCaptcha | Yes |
+| Cloudflare Turnstile | Yes |
+
+All are handled on by default — no `captcha_type` parameter or per-widget configuration.
+
+## Get started
+
+There is nothing to turn on. CAPTCHA solving comes with every session. Start one:
+
+
+
+
+Browser Use Cloud lands **81%** — ahead of every other cloud browser, and far above a plain headless browser (2%).
+
+We get there by forking Chromium rather than patching detection signals with stealth plugins, so the signals never appear in the first place. [Here's why that approach holds up as anti-bot systems tighten](https://browser-use.com/posts/bot-detection).
+
## What's included
-Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed.
+Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. [Create a browser session](/cloud/browser/create) and it is already on.
- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors.
- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster.
- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services.
+Stealth keeps most challenges from ever appearing. When one does, it is solved automatically — see [CAPTCHA solving](/cloud/browser/captcha) for per-vendor success rates.
+
## Residential proxies
-Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration.
\ No newline at end of file
+Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration.
+
+## Further reading
+
+- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers
+- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark)
+- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection)
+- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra)
\ No newline at end of file
diff --git a/docs/cloud/guides/authentication.mdx b/docs/cloud/guides/authentication.mdx
index 7b5b776c8..01f5925db 100644
--- a/docs/cloud/guides/authentication.mdx
+++ b/docs/cloud/guides/authentication.mdx
@@ -4,41 +4,118 @@ description: "Persistent browser state — cookies, localStorage, saved password
icon: user
---
+Create a profile, then pass its `profile_id` to `run()` — the agent opens a browser seeded from that profile and runs your task on it. Cookies and login state saved during the run persist, so the next run with the same profile is already logged in.
+
+
+
+Browser Use Cloud lands **81%** — ahead of every other cloud browser, and far above a plain headless browser (2%).
+
+We get there by forking Chromium rather than patching detection signals with stealth plugins, so the signals never appear in the first place. [Here's why that approach holds up as anti-bot systems tighten](https://browser-use.com/posts/bot-detection).
+
## What's included
-Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed.
+Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. [Create a browser session](https://docs.browser-use.com/cloud/browser/create) and it is already on.
- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors.
- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster.
- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services.
+Stealth keeps most challenges from ever appearing. When one does, it is solved automatically — see [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) for per-vendor success rates.
+
## Residential proxies
Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration.
+## Further reading
+
+- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers
+- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark)
+- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection)
+- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra)
+
+
+# CAPTCHA Solving
+Source: https://docs.browser-use.com/cloud/browser/captcha
+
+
+Browser Use remote browsers have **automatic CAPTCHA solving** built in. There is nothing to configure — on the browser, the attached agent, or your automation library (Playwright, Puppeteer, Selenium). It is on by default on every plan, including the [free tier](https://docs.browser-use.com/cloud/pricing).
+
+The best defense is not tripping a challenge in the first place — that is what [stealth](https://docs.browser-use.com/cloud/browser/stealth) handles (anti-fingerprinting and bot-detection bypass). This page covers what happens when a CAPTCHA or anti-bot system appears anyway: we solve it, and we lead the field on success rate.
+
+## Success rate by vendor
+
+Across the anti-bot and CAPTCHA systems agents hit most, Browser Use Cloud has the **highest overall success rate at 81%** — and the best against **Cloudflare (93%)** and **PerimeterX (81%)**.
+
+| Protection | Browser Use Cloud |
+| --- | --- |
+| Overall | **81%** |
+| Cloudflare | **93%** |
+| PerimeterX | **81%** |
+| Akamai | 85% |
+| DataDome | 69% |
+| reCAPTCHA | 80% |
+
+
+
+
+
+Full methodology in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark).
+
+## Supported CAPTCHA types
+
+These are the interactive CAPTCHA widgets solved automatically, distinct from the anti-bot systems above (Cloudflare, DataDome, PerimeterX, Akamai) that gate a site before a widget ever appears:
+
+| CAPTCHA type | Solved automatically |
+| --- | --- |
+| reCAPTCHA v2 (checkbox / image challenge) | Yes |
+| reCAPTCHA v3 (score-based) | Yes |
+| hCaptcha | Yes |
+| Cloudflare Turnstile | Yes |
+
+All are handled on by default — no `captcha_type` parameter or per-widget configuration.
+
+## Get started
+
+There is nothing to turn on. CAPTCHA solving comes with every session. Start one:
+
+SDK, REST, or a single WebSocket URL.
+Playwright, Puppeteer, or Selenium over CDP.
+What the hardened Chromium fork does.
+Residential IPs in 195+ countries, on by default.
+
+## FAQ
+
+**Does the open-source library solve CAPTCHAs?**
+
+Without remote browsers, [open-source](https://github.com/browser-use/browser-use) agents have no stealth or CAPTCHA solving. Giving your agent stealth is easy: run it on a remote browser with a single parameter. See [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent).
+
+**Can I use a third-party CAPTCHA solver?**
+
+No, we do not support third-party CAPTCHA solver plugins on the browser. If your CAPTCHAs are not being solved properly, reach out and we will look into it.
+
+**Do I need to enable anything for CAPTCHA solving?**
+
+No. Remote browsers solve CAPTCHAs for you automatically.
+
+## Further reading
+
+- [Prove you are a robot: CAPTCHAs for agents](https://browser-use.com/posts/prove-you-are-a-robot)
+- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection)
+
# Proxies
Source: https://docs.browser-use.com/cloud/browser/proxies
@@ -1080,12 +1682,12 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
browser = await client.browsers.create(
-custom_proxy={
- "host": "proxy.example.com",
- "port": 8080,
- "username": "user",
- "password": "pass",
-},
+ custom_proxy={
+ "host": "proxy.example.com",
+ "port": 8080,
+ "username": "user",
+ "password": "pass",
+ },
)
```
```typescript TypeScript
@@ -1094,14 +1696,119 @@ import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse();
const browser = await client.browsers.create({
customProxy: {
-host: "proxy.example.com",
-port: 8080,
-username: "user",
-password: "pass",
+ host: "proxy.example.com",
+ port: 8080,
+ username: "user",
+ password: "pass",
},
});
```
+## Blocked? Get a fresh IP
+
+Browser Use does not rotate the IP within a running session. When a site starts blocking you, stop the session and create a new one — each session gets a fresh residential IP from the country pool automatically.
+
+```python Python
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+client = AsyncBrowserUse()
+
+async def with_fresh_ip(country="us", profile_id=None):
+ # Stop-and-recreate is how you get a new IP; reattach a profile to keep login state.
+ browser = await client.browsers.create(proxy_country_code=country, profile_id=profile_id)
+ return browser
+```
+```typescript TypeScript
+import { BrowserUse } from "browser-use-sdk/v3";
+
+const client = new BrowserUse();
+
+async function withFreshIp(country = "us", profileId?: string) {
+ // Stop-and-recreate is how you get a new IP; reattach a profile to keep login state.
+ return client.browsers.create({ proxyCountryCode: country, profileId });
+}
+```
+
+- **New session = new IP.** Recreating the browser is the supported way to rotate.
+- **Keep your login across the rotation** by passing the same [`profile_id`](https://docs.browser-use.com/cloud/guides/authentication) — the fresh IP loads the saved cookies and localStorage.
+- **Switch country** (`proxy_country_code`) to leave a blocked regional pool entirely.
+- **Custom proxies** can rotate per request on their side — use the `custom_proxy` config above with a rotating endpoint.
+
+## Further reading
+
+- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) — how proxy quality affects bypass rates
+
+
+# Screenshots
+Source: https://docs.browser-use.com/cloud/browser/screenshots
+
+
+A cloud browser session is a normal CDP endpoint, so screenshots work the way your framework takes them, and they save wherever your code runs.
+
+## Where screenshots are saved
+
+The most-asked question first: screenshots taken through Playwright or Puppeteer are written by *your* code, to a path *you* choose. Nothing is stored on the session unless you enable [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads).
+
+```python Python
+from playwright.async_api import async_playwright
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+client = AsyncBrowserUse()
+browser = await client.browsers.create()
+
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(browser.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ await page.screenshot(path="shots/example.png") # your machine, your path
+
+await client.browsers.stop(browser.id)
+```
+```typescript TypeScript
+import { chromium } from "playwright";
+import { BrowserUse } from "browser-use-sdk/v3";
+
+const client = new BrowserUse();
+const browser = await client.browsers.create();
+
+const pw = await chromium.connectOverCDP(browser.cdpUrl);
+const page = pw.contexts()[0].pages()[0];
+await page.goto("https://example.com");
+await page.screenshot({ path: "shots/example.png" });
+
+await client.browsers.stop(browser.id);
+```
+
+## Full page, not just the viewport
+
+By default a screenshot captures the visible viewport. For the whole page, top to bottom:
+
+```python
+await page.screenshot(path="full.png", full_page=True)
+```
+
+Playwright stitches the scroll automatically. The result contains page content only, no URL bar or browser chrome, because CDP screenshots capture the rendered page, not the window.
+
+## Resolution
+
+Screenshot dimensions follow the browser's screen size, set at [session creation](https://docs.browser-use.com/cloud/browser/create) with `browserScreenWidth` and `browserScreenHeight` (320–6144 × 320–3456). Set them explicitly if screenshots must match a target resolution:
+
+```python
+browser = await client.browsers.create(browser_screen_width=1920, browser_screen_height=1080)
+```
+
+{/* TEAM REVIEW: document the default screen size when width/height are omitted, and whether recording resolution (1920x1080 reported by users) can differ from screenshot resolution — a user reported 1512x770 screenshots vs 1920x1080 recordings. */}
+
+## Screenshots vs recording
+
+Screenshots are moments; [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads) is the whole session as video (`enableRecording: true` at create, `recordingUrl` after stop). For debugging agent behavior, recording is usually what you want; for artifacts and QA evidence, screenshots.
+
+## From agent tasks
+
+Ask the agent to take screenshots as part of a task and collect them from the run's [workspace files](https://docs.browser-use.com/cloud/agent/workspaces).
+
+{/* TEAM REVIEW: add the exact API for retrieving agent step screenshots (the v1 /screenshots endpoint users reference) and note whether those images carry element highlight overlays — users ask for unmarked versions. */}
+
# Live preview & recording
Source: https://docs.browser-use.com/cloud/browser/live-preview
@@ -1194,14 +1901,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
result = await client.run(
-"Check how many GitHub stars browser-use has",
-enable_recording=True,
+ "Check how many GitHub stars browser-use has",
+ enable_recording=True,
)
# Waits up to 15s for recording to be ready. Returns [] if no browser was opened.
urls = await client.sessions.wait_for_recording(result.id)
for url in urls:
-print(url) # presigned MP4 download URL
+ print(url) # presigned MP4 download URL
```
```typescript TypeScript
import { BrowserUse } from "browser-use-sdk/v3";
@@ -1242,29 +1949,32 @@ console.log(stopped.recordingUrl); // presigned MP4 download URL
-# Playwright, Puppeteer, Selenium
-Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium
+# Playwright
+Source: https://docs.browser-use.com/cloud/browser/playwright
+
+Run your Playwright scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
-Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
+When to use this:
+- You have existing Playwright scripts and want to run them on stealth infrastructure
+- You need pixel-perfect control (screenshots, specific click coordinates, form filling)
+- You want to combine agent tasks with manual browser automation
## Option 1: WebSocket URL (no SDK)
Connect with a single URL. All configuration is passed as query parameters.
-### Playwright
-
```python Python
from playwright.async_api import async_playwright
WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
async with async_playwright() as p:
-browser = await p.chromium.connect_over_cdp(WSS_URL)
-page = browser.contexts[0].pages[0]
-await page.goto("https://example.com")
-print(await page.title())
-await browser.close()
+ browser = await p.chromium.connect_over_cdp(WSS_URL)
+ page = browser.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ print(await page.title())
+ await browser.close()
# Browser is automatically stopped when the WebSocket disconnects
```
```typescript TypeScript
@@ -1280,40 +1990,7 @@ await browser.close();
// Browser is automatically stopped when the WebSocket disconnects
```
-### Puppeteer
-
-```typescript
-import puppeteer from "puppeteer-core";
-
-const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us";
-
-const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL });
-const [page] = await browser.pages();
-await page.goto("https://example.com");
-console.log(await page.title());
-await browser.close();
-```
-
-### Selenium
-
-Selenium requires a local WebSocket proxy to connect to Browser Use's remote CDP endpoint. Use [selenium-wire](https://github.com/wkeeling/selenium-wire) or connect through Playwright's CDP bridge instead:
-
-```python
-from playwright.sync_api import sync_playwright
-
-WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
-
-with sync_playwright() as p:
-browser = p.chromium.connect_over_cdp(WSS_URL)
-page = browser.contexts[0].pages[0]
-page.goto("https://example.com")
-print(page.title())
-browser.close()
-```
-
- Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use Playwright or Puppeteer instead.
-
-## Query parameters
+### Query parameters
| Parameter | Type | Description |
|-----------|------|-------------|
@@ -1326,9 +2003,7 @@ browser.close()
## Option 2: SDK
-Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer.
-
-### Playwright
+Create a browser via the SDK, get a `cdp_url`, and connect. The SDK also gives you a `live_url` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview).
```python Python
from browser_use_sdk.v3 import AsyncBrowserUse
@@ -1340,11 +2015,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com
print(browser.live_url) # https://live.browser-use.com?wss=...
async with async_playwright() as p:
-pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url)
-page = pw_browser.contexts[0].pages[0]
-await page.goto("https://example.com")
-print(await page.title())
-await pw_browser.close()
+ pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url)
+ page = pw_browser.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ print(await page.title())
+ await pw_browser.close()
await client.browsers.stop(browser.id)
```
@@ -1366,68 +2041,401 @@ await pwBrowser.close();
await client.browsers.stop(browser.id);
```
-### Puppeteer
+### Create response
-```typescript
-import { BrowserUse } from "browser-use-sdk/v3";
-import puppeteer from "puppeteer-core";
+`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with:
-const client = new BrowserUse();
-const browser = await client.browsers.create();
+```json
+{
+ "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e",
+ "status": "active",
+ "liveUrl": "https://live.browser-use.com?wss=...",
+ "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com",
+ "timeoutAt": "2026-07-14T20:15:00Z",
+ "startedAt": "2026-07-14T20:00:00Z",
+ "finishedAt": null,
+ "proxyUsedMb": "0.0",
+ "proxyCost": "0.0",
+ "browserCost": "0.0",
+ "agentSessionId": null,
+ "recordingUrl": null
+}
+```
-// Puppeteer needs the WebSocket URL from /json/version
-const resp = await fetch(`${browser.cdpUrl}/json/version`);
-const { webSocketDebuggerUrl } = await resp.json();
+Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`) and snake_case in the Python SDK (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable — check them before connecting.
-const pwBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
-const [page] = await pwBrowser.pages();
-await page.goto("https://example.com");
-console.log(await page.title());
-await pwBrowser.close();
+### Stopping a session over REST
-await client.browsers.stop(browser.id);
+There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update:
+
+```bash
+curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \
+ -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"action": "stop"}'
```
- Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+## Gotchas
+ Use `connect_over_cdp()` / `connectOverCDP()`, **not** `connect()`. Playwright's `connect()` expects a Playwright-protocol server and fails against a CDP endpoint with an opaque `Protocol error (Browser.getVersion)`.
-# Profiles
-Source: https://docs.browser-use.com/cloud/guides/authentication
+- **Reuse the existing context.** The session already has a context and page open — use `browser.contexts[0].pages[0]` instead of `browser.new_context()`, so you keep the stealth fingerprint and any loaded [profile](https://docs.browser-use.com/cloud/browser/playwright#query-parameters).
+- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `pw_browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session.
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Puppeteer
+Source: https://docs.browser-use.com/cloud/browser/puppeteer
+
+
+Run your Puppeteer scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
+
+When to use this:
+- You have existing Puppeteer scripts and want to run them on stealth infrastructure
+- You want low-level CDP control from Node.js without managing Chrome yourself
+- You want to combine agent tasks with manual browser automation
+
+## Option 1: WebSocket URL (no SDK)
+
+Connect with a single URL. All configuration is passed as query parameters.
+
+```typescript
+import puppeteer from "puppeteer-core";
+
+const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us";
+
+const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL });
+const [page] = await browser.pages();
+await page.goto("https://example.com");
+console.log(await page.title());
+await browser.close();
+// Browser is automatically stopped when the WebSocket disconnects
+```
+
+### Query parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `apiKey` | `string` | **Required.** Your Browser Use API key. |
+| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. |
+| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). |
+| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). |
+| `browserScreenWidth` | `int` | Browser width in pixels. |
+| `browserScreenHeight` | `int` | Browser height in pixels. |
+
+## Option 2: SDK
+
+Create a browser via the SDK, then resolve the WebSocket endpoint. Unlike Playwright, Puppeteer can't connect to an HTTP CDP URL directly — fetch `/json/version` to get the `webSocketDebuggerUrl` first.
+
+```typescript
+import { BrowserUse } from "browser-use-sdk/v3";
+import puppeteer from "puppeteer-core";
+
+const client = new BrowserUse();
+const browser = await client.browsers.create();
+
+// Puppeteer needs the WebSocket URL from /json/version
+const resp = await fetch(`${browser.cdpUrl}/json/version`);
+const { webSocketDebuggerUrl } = await resp.json();
+
+const pptrBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
+const [page] = await pptrBrowser.pages();
+await page.goto("https://example.com");
+console.log(await page.title());
+await pptrBrowser.close();
+
+await client.browsers.stop(browser.id);
+```
+
+The SDK also gives you a `liveUrl` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview).
+
+### Create response
+
+`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with:
+
+```json
+{
+ "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e",
+ "status": "active",
+ "liveUrl": "https://live.browser-use.com?wss=...",
+ "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com",
+ "timeoutAt": "2026-07-14T20:15:00Z",
+ "startedAt": "2026-07-14T20:00:00Z",
+ "finishedAt": null,
+ "proxyUsedMb": "0.0",
+ "proxyCost": "0.0",
+ "browserCost": "0.0",
+ "agentSessionId": null,
+ "recordingUrl": null
+}
+```
+
+Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`). `cdpUrl` and `liveUrl` are nullable — check them before connecting.
+
+### Stopping a session over REST
+
+There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update:
+
+```bash
+curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \
+ -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"action": "stop"}'
+```
+
+## Gotchas
+
+- **Use `puppeteer-core`.** It's the connect-only package — installing full `puppeteer` downloads a local Chromium you'll never use.
+- **`browserWSEndpoint` must be a `ws://`/`wss://` URL.** Passing the SDK's HTTPS `cdpUrl` directly fails; resolve it via `/json/version` as shown above.
+- **Viewport.** Puppeteer applies its own 800×600 default viewport after connecting. Pass `defaultViewport: null` to `puppeteer.connect()` to keep the browser's real window size.
+- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session.
+
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Selenium
+Source: https://docs.browser-use.com/cloud/browser/selenium
+
+
+Browser Use's cloud browsers speak Chrome DevTools Protocol (CDP) over a remote WebSocket. Selenium can't consume that natively: its `debugger_address` option only supports local `host:port` connections, not remote `wss://` URLs.
+
+You have two practical paths.
+
+## Recommended: bridge through a CDP client
+
+If you're migrating Selenium scripts, connect through Playwright's sync API — the page-automation model (navigate, locate, click, read) maps one-to-one, and you get the [hardened stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) with no configuration.
+
+```python
+from playwright.sync_api import sync_playwright
+
+WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
+
+with sync_playwright() as p:
+ browser = p.chromium.connect_over_cdp(WSS_URL)
+ page = browser.contexts[0].pages[0]
+ page.goto("https://example.com")
+ print(page.title())
+ browser.close()
+# Browser is automatically stopped when the WebSocket disconnects
+```
+
+Common Selenium → Playwright equivalents:
+
+| Selenium | Playwright (sync) |
+|---|---|
+| `driver.get(url)` | `page.goto(url)` |
+| `driver.find_element(By.CSS_SELECTOR, s)` | `page.locator(s)` |
+| `element.click()` | `page.locator(s).click()` |
+| `element.send_keys(text)` | `page.locator(s).fill(text)` |
+| `driver.title` | `page.title()` |
+| `WebDriverWait(...).until(...)` | built-in auto-waiting |
+| `driver.quit()` | `browser.close()` |
+
+### Query parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `apiKey` | `string` | **Required.** Your Browser Use API key. |
+| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. |
+| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). |
+| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). |
+| `browserScreenWidth` | `int` | Browser width in pixels. |
+| `browserScreenHeight` | `int` | Browser height in pixels. |
+
+## Alternative: keep Selenium with a local proxy
+
+If you must keep the Selenium API, run a local WebSocket-to-TCP proxy so Chrome's remote debugging endpoint appears as a local `host:port`, e.g. via [selenium-wire](https://github.com/wkeeling/selenium-wire). This adds a moving part we don't manage — for new code, prefer the CDP bridge above.
+
+ Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use [Playwright](https://docs.browser-use.com/cloud/browser/playwright) or [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) instead.
+
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Cloud browser + open source agent
+Source: https://docs.browser-use.com/cloud/browser/open-source-agent
+
+
+The [open-source library](/open-source/introduction) runs the agent on your machine. By default it also runs the *browser* on your machine, which means no stealth, no residential proxy, and no CAPTCHA solving. This page connects the two: keep your local agent code, point it at a cloud browser.
+
+## Connect by CDP URL
+
+Create a cloud browser, then pass its CDP URL to the library's `Browser`:
+
+```python
+import asyncio
+from browser_use import Agent, Browser, ChatOpenAI
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+async def main():
+ client = AsyncBrowserUse()
+ cloud_browser = await client.browsers.create(proxy_country_code="us")
+
+ try:
+ agent = Agent(
+ task="Find the current price of iPhone 16 on amazon.de",
+ llm=ChatOpenAI(model="gpt-4o"),
+ browser=Browser(cdp_url=cloud_browser.cdp_url),
+ )
+ await agent.run()
+ finally:
+ await client.browsers.stop(cloud_browser.id)
+
+asyncio.run(main())
+```
+
+The agent behaves exactly as it does locally. The browser it drives is a [stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) with [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies), and you can watch it work through the session's `live_url`.
+
+{/* TEAM REVIEW: confirm the `use_cloud=True` shorthand on Browser() — parameter name, minimum library version, and whether it should be the primary example instead of the cdp_url form. */}
+
+## What you get, what you keep
+
+| | Stays yours | Comes from Cloud |
+|---|---|---|
+| Agent loop, prompts, custom tools | ✓ | |
+| LLM choice and API keys | ✓ | |
+| Browser runtime | | ✓ stealth Chromium |
+| Proxy / IP | | ✓ residential, 195+ countries |
+| CAPTCHA handling | | ✓ automatic |
+| Live view and recording | | ✓ per session |
+
+Billing: only the browser session ($0.02/hour plus proxy data). Your LLM tokens go to your own provider.
+
+## Related
+
+- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all session parameters
+- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud) — the full decision guide
+- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions) — always stop sessions when done
+
+
+# Profiles
+Source: https://docs.browser-use.com/cloud/guides/authentication
+
+
+Create a profile, then pass its `profile_id` to `run()` — the agent opens a browser seeded from that profile and runs your task on it. Cookies and login state saved during the run persist, so the next run with the same profile is already logged in.
```python Python
from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
+
+# 1. Create a profile (stores cookies + login state across runs)
profile = await client.profiles.create(name="user-id-1")
-# or search existing
+# or reuse an existing one:
# profile = (await client.profiles.list(query="user-id-1")).items[0]
-session = await client.sessions.create(profile_id=profile.id)
-result = await client.run("Check browser-use github stars", session_id=session.id)
-print(result.output)
-# Always stop the session to persist profile state
-await client.sessions.stop(session.id)
+# 2. Run the agent — profile_id attaches a browser seeded from the profile
+result = await client.run(
+ "Go to example.com and return the page title",
+ profile_id=profile.id,
+)
+print(result.output) # -> "Example Domain"
+
+# 3. Reuse the same profile later — saved login/cookies carry over
+followup = await client.run("Check my GitHub notifications", profile_id=profile.id)
```
```typescript TypeScript
import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse();
+
+// 1. Create a profile (stores cookies + login state across runs)
const profile = await client.profiles.create({ name: "user-id-1" });
-// or search existing
+// or reuse an existing one:
// const profile = (await client.profiles.list({ query: "user-id-1" })).items[0];
-const session = await client.sessions.create({ profileId: profile.id });
-const result = await client.run("Check browser-use github stars", {
- sessionId: session.id,
+
+// 2. Run the agent — profileId attaches a browser seeded from the profile
+const result = await client.run("Go to example.com and return the page title", {
+ profileId: profile.id,
});
-console.log(result.output);
+console.log(result.output); // -> "Example Domain"
-// Always stop the session to persist profile state
-await client.sessions.stop(session.id);
+// 3. Reuse the same profile later — saved login/cookies carry over
+const followup = await client.run("Check my GitHub notifications", { profileId: profile.id });
```
+Passing `profile_id` to `run()` provisions the browser and runs the agent in one call — no separate session step. Profile state is saved automatically when the run ends.
+
View your profile IDs at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=profiles).
+## Persist state on a browser you drive (CDP)
+
+Profiles work the same whether the agent drives or you do. Pass `profile_id` to `browsers.create()`, set state over CDP, then **stop the browser to flush cookies and localStorage into the profile**. Reconnect later with the same `profile_id` and the state is there.
+
+```python Python
+from browser_use_sdk.v3 import AsyncBrowserUse
+from playwright.async_api import async_playwright
+
+client = AsyncBrowserUse()
+profile = await client.profiles.create(name="persist-demo")
+
+# Session 1 — write state, then stop to persist
+b1 = await client.browsers.create(profile_id=profile.id)
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(b1.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://en.wikipedia.org")
+ await page.evaluate("localStorage.setItem('demo', 'hello')")
+ await pw.close()
+await client.browsers.stop(b1.id) # flushes state into the profile
+
+# Session 2 — same profile, state is back
+b2 = await client.browsers.create(profile_id=profile.id)
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(b2.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://en.wikipedia.org")
+ value = await page.evaluate("localStorage.getItem('demo')")
+ print(value) # -> hello
+ await pw.close()
+await client.browsers.stop(b2.id)
+```
+```typescript TypeScript
+import { BrowserUse } from "browser-use-sdk/v3";
+import { chromium } from "playwright";
+
+const client = new BrowserUse();
+const profile = await client.profiles.create({ name: "persist-demo" });
+
+// Session 1 — write state, then stop to persist
+const b1 = await client.browsers.create({ profileId: profile.id });
+let pw = await chromium.connectOverCDP(b1.cdpUrl);
+let page = pw.contexts()[0].pages()[0];
+await page.goto("https://en.wikipedia.org");
+await page.evaluate(() => localStorage.setItem("demo", "hello"));
+await pw.close();
+await client.browsers.stop(b1.id); // flushes state into the profile
+
+// Session 2 — same profile, state is back
+const b2 = await client.browsers.create({ profileId: profile.id });
+pw = await chromium.connectOverCDP(b2.cdpUrl);
+page = pw.contexts()[0].pages()[0];
+await page.goto("https://en.wikipedia.org");
+console.log(await page.evaluate(() => localStorage.getItem("demo"))); // -> hello
+await pw.close();
+await client.browsers.stop(b2.id);
+```
+
+ Use a site that actually sets cookies/localStorage to verify persistence — `example.com` sets none, so it is a poor test target.
+
## Manage profiles
```python Python
@@ -1437,7 +2445,7 @@ profile = await client.profiles.create(name="work-account")
# List all
response = await client.profiles.list()
for p in response.items:
-print(p.id, p.name)
+ print(p.id, p.name)
# Search by name
response = await client.profiles.list(query="user-id-1")
@@ -1480,10 +2488,14 @@ await client.profiles.delete(profileId);
- **Per-user profiles:** Create one profile per end-user. Query by name to get the profile ID, or store a mapping between your users and their profile IDs in your database.
- Profile state is only saved when the session ends. Always call `sessions.stop()` when you are done — if a session is left open or times out, changes may not be persisted. Every code path that uses a profile must stop the session, including error handlers.
+ Profile state is saved when the run ends — call `sessions.stop()` (agent) or `browsers.stop()` (CDP) when you are done. Both paths persist; a session left open or timed out may not save. Stop in a `finally` so every code path, including error handlers, persists.
+## Further reading
-# Sync local and cloud cookies
+- [How to authenticate AI web agents](https://browser-use.com/posts/web-agent-authentication)
+
+
+# Profiles / Cookie sync
Source: https://docs.browser-use.com/cloud/guides/profile-sync
@@ -1603,8 +2615,8 @@ print(f"Live view: {session.live_url}")
# Agent navigates to login
result = await client.run(
-"Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA",
-session_id=session.id,
+ "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA",
+ session_id=session.id,
)
# Human completes 2FA in the live view
@@ -1612,8 +2624,8 @@ input("Complete 2FA in the live view, then press Enter...")
# Agent continues
result = await client.run(
-"You are now logged in. Go to the dashboard and export the monthly report",
-session_id=session.id,
+ "You are now logged in. Go to the dashboard and export the monthly report",
+ session_id=session.id,
)
print(result.output)
await client.sessions.stop(session.id)
@@ -1662,14 +2674,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
result = await client.run(
-"""
-1. Go to example.com/signup
-2. Sign up with the agent's email address (use the email available to you)
-3. Check your email inbox for the verification code
-4. Enter the code on the website
-5. Complete the registration
-""",
-agentmail=True, # default, shown for clarity
+ """
+ 1. Go to example.com/signup
+ 2. Sign up with the agent's email address (use the email available to you)
+ 3. Check your email inbox for the verification code
+ 4. Enter the code on the website
+ 5. Complete the registration
+ """,
+ agentmail=True, # default, shown for clarity
)
print(result.output)
```
@@ -1719,16 +2731,16 @@ client = AsyncBrowserUse()
totp_secret = "JBSWY3DPEHPK3PXP"
result = await client.run(
-f"""
-Log into example.com with username user@example.com and password mypassword.
-When prompted for a 2FA code, generate one using pyotp:
+ f"""
+ Log into example.com with username user@example.com and password mypassword.
+ When prompted for a 2FA code, generate one using pyotp:
-import pyotp
-totp = pyotp.TOTP("{totp_secret}")
-code = totp.now()
+ import pyotp
+ totp = pyotp.TOTP("{totp_secret}")
+ code = totp.now()
-Enter the generated code.
-""",
+ Enter the generated code.
+ """,
)
print(result.output)
```
@@ -1772,100 +2784,502 @@ Use **Agent Mail** (enabled by default). For end-client scenarios, have them for
Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed.
-# OpenClaw
-Source: https://docs.browser-use.com/cloud/tutorials/integrations/openclaw
+# Webhooks
+Source: https://docs.browser-use.com/cloud/guides/webhooks
-[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.
+Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks).
-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.
+## Events
-## Option 1: Cloud Browser via CDP
+| Event | When |
+|-------|------|
+| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) |
+| `test` | Webhook test ping |
-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.
+## Payload
-Browser Use exposes a WebSocket CDP URL. OpenClaw connects to it like any remote browser — no SDK or extra dependencies needed.
+```json
+{
+ "type": "agent.task.status_update",
+ "timestamp": "2025-01-15T10:30:00Z",
+ "payload": {
+ "task_id": "task_abc123",
+ "session_id": "session_xyz",
+ "status": "idle",
+ "metadata": {}
+ }
+}
+```
-### Setup
+## Signature verification
-**1. Get your API key**
+Every webhook request includes two headers:
-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).
+- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload
+- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent
-**2. Add a Browser Use profile**
+The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks.
-Open `~/.openclaw/openclaw.json` and add a `browser-use` profile:
+```python Python
+import hashlib
+import hmac
+import json
+import time
-```json5
-{
- browser: {
-enabled: true,
-defaultProfile: "browser-use",
-remoteCdpTimeoutMs: 3000,
-remoteCdpHandshakeTimeoutMs: 5000,
-profiles: {
- "browser-use": {
- cdpUrl: "wss://connect.browser-use.com?apiKey=
+
+
+Browser Use Cloud lands **81%** — ahead of every other cloud browser, and far above a plain headless browser (2%).
+
+We get there by forking Chromium rather than patching detection signals with stealth plugins, so the signals never appear in the first place. [Here's why that approach holds up as anti-bot systems tighten](https://browser-use.com/posts/bot-detection).
+
## What's included
-Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed.
+Every cloud browser session runs in a hardened Chromium fork with stealth enabled by default — no configuration needed. [Create a browser session](https://docs.browser-use.com/cloud/browser/create) and it is already on.
- **Anti-detect browser fingerprinting** — Canvas, WebGL, fonts, navigator, and other browser fingerprints are randomized per session to appear as a real user. Passes CreepJS, BrowserLeaks, and other fingerprint detectors.
- **Ad and cookie banner blocking** — Banners are dismissed automatically so the agent sees clean pages and executes faster.
- **Cloudflare / anti-bot bypass** — Works on sites protected by Cloudflare, PerimeterX, and other bot detection services.
+Stealth keeps most challenges from ever appearing. When one does, it is solved automatically — see [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) for per-vendor success rates.
+
## Residential proxies
Residential proxies are enabled by default across 195+ countries. This makes browser sessions appear as real users from the target geography. See [Proxies](https://docs.browser-use.com/cloud/browser/proxies) for details on geo-targeting and custom proxy configuration.
+## Further reading
+
+- [Benchmarks](https://browser-use.com/benchmarks) — 84.8% on BrowserBench and 81% bypass on high-security sites, against other providers
+- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark)
+- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection)
+- [Stealth Browser Infrastructure](https://browser-use.com/posts/browser-infra)
+
+
+# CAPTCHA Solving
+Source: https://docs.browser-use.com/cloud/browser/captcha
+
+
+Browser Use remote browsers have **automatic CAPTCHA solving** built in. There is nothing to configure — on the browser, the attached agent, or your automation library (Playwright, Puppeteer, Selenium). It is on by default on every plan, including the [free tier](https://docs.browser-use.com/cloud/pricing).
+
+The best defense is not tripping a challenge in the first place — that is what [stealth](https://docs.browser-use.com/cloud/browser/stealth) handles (anti-fingerprinting and bot-detection bypass). This page covers what happens when a CAPTCHA or anti-bot system appears anyway: we solve it, and we lead the field on success rate.
+
+## Success rate by vendor
+
+Across the anti-bot and CAPTCHA systems agents hit most, Browser Use Cloud has the **highest overall success rate at 81%** — and the best against **Cloudflare (93%)** and **PerimeterX (81%)**.
+
+| Protection | Browser Use Cloud |
+| --- | --- |
+| Overall | **81%** |
+| Cloudflare | **93%** |
+| PerimeterX | **81%** |
+| Akamai | 85% |
+| DataDome | 69% |
+| reCAPTCHA | 80% |
+
+
+
+
+
+Full methodology in [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark).
+
+## Supported CAPTCHA types
+
+These are the interactive CAPTCHA widgets solved automatically, distinct from the anti-bot systems above (Cloudflare, DataDome, PerimeterX, Akamai) that gate a site before a widget ever appears:
+
+| CAPTCHA type | Solved automatically |
+| --- | --- |
+| reCAPTCHA v2 (checkbox / image challenge) | Yes |
+| reCAPTCHA v3 (score-based) | Yes |
+| hCaptcha | Yes |
+| Cloudflare Turnstile | Yes |
+
+All are handled on by default — no `captcha_type` parameter or per-widget configuration.
+
+## Get started
+
+There is nothing to turn on. CAPTCHA solving comes with every session. Start one:
+
+SDK, REST, or a single WebSocket URL.
+Playwright, Puppeteer, or Selenium over CDP.
+What the hardened Chromium fork does.
+Residential IPs in 195+ countries, on by default.
+
+## FAQ
+
+**Does the open-source library solve CAPTCHAs?**
+
+Without remote browsers, [open-source](https://github.com/browser-use/browser-use) agents have no stealth or CAPTCHA solving. Giving your agent stealth is easy: run it on a remote browser with a single parameter. See [Cloud browser + open source agent](https://docs.browser-use.com/cloud/browser/open-source-agent).
+
+**Can I use a third-party CAPTCHA solver?**
+
+No, we do not support third-party CAPTCHA solver plugins on the browser. If your CAPTCHAs are not being solved properly, reach out and we will look into it.
+
+**Do I need to enable anything for CAPTCHA solving?**
+
+No. Remote browsers solve CAPTCHAs for you automatically.
+
+## Further reading
+
+- [Prove you are a robot: CAPTCHAs for agents](https://browser-use.com/posts/prove-you-are-a-robot)
+- [Browser agent bot detection is about to change](https://browser-use.com/posts/bot-detection)
+
# Proxies
Source: https://docs.browser-use.com/cloud/browser/proxies
@@ -1080,12 +1682,12 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
browser = await client.browsers.create(
-custom_proxy={
- "host": "proxy.example.com",
- "port": 8080,
- "username": "user",
- "password": "pass",
-},
+ custom_proxy={
+ "host": "proxy.example.com",
+ "port": 8080,
+ "username": "user",
+ "password": "pass",
+ },
)
```
```typescript TypeScript
@@ -1094,14 +1696,119 @@ import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse();
const browser = await client.browsers.create({
customProxy: {
-host: "proxy.example.com",
-port: 8080,
-username: "user",
-password: "pass",
+ host: "proxy.example.com",
+ port: 8080,
+ username: "user",
+ password: "pass",
},
});
```
+## Blocked? Get a fresh IP
+
+Browser Use does not rotate the IP within a running session. When a site starts blocking you, stop the session and create a new one — each session gets a fresh residential IP from the country pool automatically.
+
+```python Python
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+client = AsyncBrowserUse()
+
+async def with_fresh_ip(country="us", profile_id=None):
+ # Stop-and-recreate is how you get a new IP; reattach a profile to keep login state.
+ browser = await client.browsers.create(proxy_country_code=country, profile_id=profile_id)
+ return browser
+```
+```typescript TypeScript
+import { BrowserUse } from "browser-use-sdk/v3";
+
+const client = new BrowserUse();
+
+async function withFreshIp(country = "us", profileId?: string) {
+ // Stop-and-recreate is how you get a new IP; reattach a profile to keep login state.
+ return client.browsers.create({ proxyCountryCode: country, profileId });
+}
+```
+
+- **New session = new IP.** Recreating the browser is the supported way to rotate.
+- **Keep your login across the rotation** by passing the same [`profile_id`](https://docs.browser-use.com/cloud/guides/authentication) — the fresh IP loads the saved cookies and localStorage.
+- **Switch country** (`proxy_country_code`) to leave a blocked regional pool entirely.
+- **Custom proxies** can rotate per request on their side — use the `custom_proxy` config above with a rotating endpoint.
+
+## Further reading
+
+- [We stealth benchmarked every major cloud browser provider](https://browser-use.com/posts/stealth-benchmark) — how proxy quality affects bypass rates
+
+
+# Screenshots
+Source: https://docs.browser-use.com/cloud/browser/screenshots
+
+
+A cloud browser session is a normal CDP endpoint, so screenshots work the way your framework takes them, and they save wherever your code runs.
+
+## Where screenshots are saved
+
+The most-asked question first: screenshots taken through Playwright or Puppeteer are written by *your* code, to a path *you* choose. Nothing is stored on the session unless you enable [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads).
+
+```python Python
+from playwright.async_api import async_playwright
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+client = AsyncBrowserUse()
+browser = await client.browsers.create()
+
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(browser.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ await page.screenshot(path="shots/example.png") # your machine, your path
+
+await client.browsers.stop(browser.id)
+```
+```typescript TypeScript
+import { chromium } from "playwright";
+import { BrowserUse } from "browser-use-sdk/v3";
+
+const client = new BrowserUse();
+const browser = await client.browsers.create();
+
+const pw = await chromium.connectOverCDP(browser.cdpUrl);
+const page = pw.contexts()[0].pages()[0];
+await page.goto("https://example.com");
+await page.screenshot({ path: "shots/example.png" });
+
+await client.browsers.stop(browser.id);
+```
+
+## Full page, not just the viewport
+
+By default a screenshot captures the visible viewport. For the whole page, top to bottom:
+
+```python
+await page.screenshot(path="full.png", full_page=True)
+```
+
+Playwright stitches the scroll automatically. The result contains page content only, no URL bar or browser chrome, because CDP screenshots capture the rendered page, not the window.
+
+## Resolution
+
+Screenshot dimensions follow the browser's screen size, set at [session creation](https://docs.browser-use.com/cloud/browser/create) with `browserScreenWidth` and `browserScreenHeight` (320–6144 × 320–3456). Set them explicitly if screenshots must match a target resolution:
+
+```python
+browser = await client.browsers.create(browser_screen_width=1920, browser_screen_height=1080)
+```
+
+{/* TEAM REVIEW: document the default screen size when width/height are omitted, and whether recording resolution (1920x1080 reported by users) can differ from screenshot resolution — a user reported 1512x770 screenshots vs 1920x1080 recordings. */}
+
+## Screenshots vs recording
+
+Screenshots are moments; [recording](https://docs.browser-use.com/cloud/browser/sessions#recordings-and-downloads) is the whole session as video (`enableRecording: true` at create, `recordingUrl` after stop). For debugging agent behavior, recording is usually what you want; for artifacts and QA evidence, screenshots.
+
+## From agent tasks
+
+Ask the agent to take screenshots as part of a task and collect them from the run's [workspace files](https://docs.browser-use.com/cloud/agent/workspaces).
+
+{/* TEAM REVIEW: add the exact API for retrieving agent step screenshots (the v1 /screenshots endpoint users reference) and note whether those images carry element highlight overlays — users ask for unmarked versions. */}
+
# Live preview & recording
Source: https://docs.browser-use.com/cloud/browser/live-preview
@@ -1194,14 +1901,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
result = await client.run(
-"Check how many GitHub stars browser-use has",
-enable_recording=True,
+ "Check how many GitHub stars browser-use has",
+ enable_recording=True,
)
# Waits up to 15s for recording to be ready. Returns [] if no browser was opened.
urls = await client.sessions.wait_for_recording(result.id)
for url in urls:
-print(url) # presigned MP4 download URL
+ print(url) # presigned MP4 download URL
```
```typescript TypeScript
import { BrowserUse } from "browser-use-sdk/v3";
@@ -1242,29 +1949,32 @@ console.log(stopped.recordingUrl); // presigned MP4 download URL
-# Playwright, Puppeteer, Selenium
-Source: https://docs.browser-use.com/cloud/browser/playwright-puppeteer-selenium
+# Playwright
+Source: https://docs.browser-use.com/cloud/browser/playwright
+
+Run your Playwright scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
-Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
+When to use this:
+- You have existing Playwright scripts and want to run them on stealth infrastructure
+- You need pixel-perfect control (screenshots, specific click coordinates, form filling)
+- You want to combine agent tasks with manual browser automation
## Option 1: WebSocket URL (no SDK)
Connect with a single URL. All configuration is passed as query parameters.
-### Playwright
-
```python Python
from playwright.async_api import async_playwright
WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
async with async_playwright() as p:
-browser = await p.chromium.connect_over_cdp(WSS_URL)
-page = browser.contexts[0].pages[0]
-await page.goto("https://example.com")
-print(await page.title())
-await browser.close()
+ browser = await p.chromium.connect_over_cdp(WSS_URL)
+ page = browser.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ print(await page.title())
+ await browser.close()
# Browser is automatically stopped when the WebSocket disconnects
```
```typescript TypeScript
@@ -1280,40 +1990,7 @@ await browser.close();
// Browser is automatically stopped when the WebSocket disconnects
```
-### Puppeteer
-
-```typescript
-import puppeteer from "puppeteer-core";
-
-const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us";
-
-const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL });
-const [page] = await browser.pages();
-await page.goto("https://example.com");
-console.log(await page.title());
-await browser.close();
-```
-
-### Selenium
-
-Selenium requires a local WebSocket proxy to connect to Browser Use's remote CDP endpoint. Use [selenium-wire](https://github.com/wkeeling/selenium-wire) or connect through Playwright's CDP bridge instead:
-
-```python
-from playwright.sync_api import sync_playwright
-
-WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
-
-with sync_playwright() as p:
-browser = p.chromium.connect_over_cdp(WSS_URL)
-page = browser.contexts[0].pages[0]
-page.goto("https://example.com")
-print(page.title())
-browser.close()
-```
-
- Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use Playwright or Puppeteer instead.
-
-## Query parameters
+### Query parameters
| Parameter | Type | Description |
|-----------|------|-------------|
@@ -1326,9 +2003,7 @@ browser.close()
## Option 2: SDK
-Create a browser via the SDK, get a `cdp_url`, and connect with Playwright or Puppeteer.
-
-### Playwright
+Create a browser via the SDK, get a `cdp_url`, and connect. The SDK also gives you a `live_url` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview).
```python Python
from browser_use_sdk.v3 import AsyncBrowserUse
@@ -1340,11 +2015,11 @@ print(browser.cdp_url) # https://uuid.cdpN.browser-use.com
print(browser.live_url) # https://live.browser-use.com?wss=...
async with async_playwright() as p:
-pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url)
-page = pw_browser.contexts[0].pages[0]
-await page.goto("https://example.com")
-print(await page.title())
-await pw_browser.close()
+ pw_browser = await p.chromium.connect_over_cdp(browser.cdp_url)
+ page = pw_browser.contexts[0].pages[0]
+ await page.goto("https://example.com")
+ print(await page.title())
+ await pw_browser.close()
await client.browsers.stop(browser.id)
```
@@ -1366,68 +2041,401 @@ await pwBrowser.close();
await client.browsers.stop(browser.id);
```
-### Puppeteer
+### Create response
-```typescript
-import { BrowserUse } from "browser-use-sdk/v3";
-import puppeteer from "puppeteer-core";
+`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with:
-const client = new BrowserUse();
-const browser = await client.browsers.create();
+```json
+{
+ "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e",
+ "status": "active",
+ "liveUrl": "https://live.browser-use.com?wss=...",
+ "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com",
+ "timeoutAt": "2026-07-14T20:15:00Z",
+ "startedAt": "2026-07-14T20:00:00Z",
+ "finishedAt": null,
+ "proxyUsedMb": "0.0",
+ "proxyCost": "0.0",
+ "browserCost": "0.0",
+ "agentSessionId": null,
+ "recordingUrl": null
+}
+```
-// Puppeteer needs the WebSocket URL from /json/version
-const resp = await fetch(`${browser.cdpUrl}/json/version`);
-const { webSocketDebuggerUrl } = await resp.json();
+Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`) and snake_case in the Python SDK (`cdp_url`, `live_url`). `cdpUrl` and `liveUrl` are nullable — check them before connecting.
-const pwBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
-const [page] = await pwBrowser.pages();
-await page.goto("https://example.com");
-console.log(await page.title());
-await pwBrowser.close();
+### Stopping a session over REST
-await client.browsers.stop(browser.id);
+There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update:
+
+```bash
+curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \
+ -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"action": "stop"}'
```
- Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+## Gotchas
+ Use `connect_over_cdp()` / `connectOverCDP()`, **not** `connect()`. Playwright's `connect()` expects a Playwright-protocol server and fails against a CDP endpoint with an opaque `Protocol error (Browser.getVersion)`.
-# Profiles
-Source: https://docs.browser-use.com/cloud/guides/authentication
+- **Reuse the existing context.** The session already has a context and page open — use `browser.contexts[0].pages[0]` instead of `browser.new_context()`, so you keep the stealth fingerprint and any loaded [profile](https://docs.browser-use.com/cloud/browser/playwright#query-parameters).
+- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `pw_browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session.
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Puppeteer
+Source: https://docs.browser-use.com/cloud/browser/puppeteer
+
+
+Run your Puppeteer scripts on Browser Use's cloud browsers. Every session runs in a [hardened Chromium fork](https://docs.browser-use.com/cloud/browser/stealth) with stealth, anti-fingerprinting, and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) enabled by default — no configuration needed.
+
+When to use this:
+- You have existing Puppeteer scripts and want to run them on stealth infrastructure
+- You want low-level CDP control from Node.js without managing Chrome yourself
+- You want to combine agent tasks with manual browser automation
+
+## Option 1: WebSocket URL (no SDK)
+
+Connect with a single URL. All configuration is passed as query parameters.
+
+```typescript
+import puppeteer from "puppeteer-core";
+
+const WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us";
+
+const browser = await puppeteer.connect({ browserWSEndpoint: WSS_URL });
+const [page] = await browser.pages();
+await page.goto("https://example.com");
+console.log(await page.title());
+await browser.close();
+// Browser is automatically stopped when the WebSocket disconnects
+```
+
+### Query parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `apiKey` | `string` | **Required.** Your Browser Use API key. |
+| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. |
+| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). |
+| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). |
+| `browserScreenWidth` | `int` | Browser width in pixels. |
+| `browserScreenHeight` | `int` | Browser height in pixels. |
+
+## Option 2: SDK
+
+Create a browser via the SDK, then resolve the WebSocket endpoint. Unlike Playwright, Puppeteer can't connect to an HTTP CDP URL directly — fetch `/json/version` to get the `webSocketDebuggerUrl` first.
+
+```typescript
+import { BrowserUse } from "browser-use-sdk/v3";
+import puppeteer from "puppeteer-core";
+
+const client = new BrowserUse();
+const browser = await client.browsers.create();
+
+// Puppeteer needs the WebSocket URL from /json/version
+const resp = await fetch(`${browser.cdpUrl}/json/version`);
+const { webSocketDebuggerUrl } = await resp.json();
+
+const pptrBrowser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
+const [page] = await pptrBrowser.pages();
+await page.goto("https://example.com");
+console.log(await page.title());
+await pptrBrowser.close();
+
+await client.browsers.stop(browser.id);
+```
+
+The SDK also gives you a `liveUrl` to [watch or embed the session](https://docs.browser-use.com/cloud/browser/live-preview).
+
+### Create response
+
+`browsers.create()` wraps `POST https://api.browser-use.com/api/v3/browsers`, which returns `201` with:
+
+```json
+{
+ "id": "0d5f16f3-96cc-4d5f-a5a4-4a4d3b5f9d2e",
+ "status": "active",
+ "liveUrl": "https://live.browser-use.com?wss=...",
+ "cdpUrl": "https://0d5f16f3.cdp1.browser-use.com",
+ "timeoutAt": "2026-07-14T20:15:00Z",
+ "startedAt": "2026-07-14T20:00:00Z",
+ "finishedAt": null,
+ "proxyUsedMb": "0.0",
+ "proxyCost": "0.0",
+ "browserCost": "0.0",
+ "agentSessionId": null,
+ "recordingUrl": null
+}
+```
+
+Field names are camelCase in the REST API and TypeScript SDK (`cdpUrl`, `liveUrl`). `cdpUrl` and `liveUrl` are nullable — check them before connecting.
+
+### Stopping a session over REST
+
+There is no `POST /browsers/{id}/stop` endpoint. Stopping is an update:
+
+```bash
+curl -X PATCH "https://api.browser-use.com/api/v3/browsers/$SESSION_ID" \
+ -H "X-Browser-Use-API-Key: $BROWSER_USE_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"action": "stop"}'
+```
+
+## Gotchas
+
+- **Use `puppeteer-core`.** It's the connect-only package — installing full `puppeteer` downloads a local Chromium you'll never use.
+- **`browserWSEndpoint` must be a `ws://`/`wss://` URL.** Passing the SDK's HTTPS `cdpUrl` directly fails; resolve it via `/json/version` as shown above.
+- **Viewport.** Puppeteer applies its own 800×600 default viewport after connecting. Pass `defaultViewport: null` to `puppeteer.connect()` to keep the browser's real window size.
+- **Closing the connection vs stopping the session.** With the WebSocket URL, disconnecting stops the browser. With the SDK, `browser.close()` only disconnects your client — call `client.browsers.stop(browser.id)` to end the session.
+
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Selenium](https://docs.browser-use.com/cloud/browser/selenium) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Selenium
+Source: https://docs.browser-use.com/cloud/browser/selenium
+
+
+Browser Use's cloud browsers speak Chrome DevTools Protocol (CDP) over a remote WebSocket. Selenium can't consume that natively: its `debugger_address` option only supports local `host:port` connections, not remote `wss://` URLs.
+
+You have two practical paths.
+
+## Recommended: bridge through a CDP client
+
+If you're migrating Selenium scripts, connect through Playwright's sync API — the page-automation model (navigate, locate, click, read) maps one-to-one, and you get the [hardened stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) and [residential proxies](https://docs.browser-use.com/cloud/browser/proxies) with no configuration.
+
+```python
+from playwright.sync_api import sync_playwright
+
+WSS_URL = "wss://connect.browser-use.com?apiKey=YOUR_API_KEY&proxyCountryCode=us"
+
+with sync_playwright() as p:
+ browser = p.chromium.connect_over_cdp(WSS_URL)
+ page = browser.contexts[0].pages[0]
+ page.goto("https://example.com")
+ print(page.title())
+ browser.close()
+# Browser is automatically stopped when the WebSocket disconnects
+```
+
+Common Selenium → Playwright equivalents:
+
+| Selenium | Playwright (sync) |
+|---|---|
+| `driver.get(url)` | `page.goto(url)` |
+| `driver.find_element(By.CSS_SELECTOR, s)` | `page.locator(s)` |
+| `element.click()` | `page.locator(s).click()` |
+| `element.send_keys(text)` | `page.locator(s).fill(text)` |
+| `driver.title` | `page.title()` |
+| `WebDriverWait(...).until(...)` | built-in auto-waiting |
+| `driver.quit()` | `browser.close()` |
+
+### Query parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `apiKey` | `string` | **Required.** Your Browser Use API key. |
+| `proxyCountryCode` | `string` | Proxy country code (e.g. `us`, `de`, `jp`). 195+ countries. |
+| `profileId` | `string` | Load a saved browser profile (cookies, localStorage). |
+| `timeout` | `int` | Session timeout in minutes. Default: 15. Max: 240 (4 hours). |
+| `browserScreenWidth` | `int` | Browser width in pixels. |
+| `browserScreenHeight` | `int` | Browser height in pixels. |
+
+## Alternative: keep Selenium with a local proxy
+
+If you must keep the Selenium API, run a local WebSocket-to-TCP proxy so Chrome's remote debugging endpoint appears as a local `host:port`, e.g. via [selenium-wire](https://github.com/wkeeling/selenium-wire). This adds a moving part we don't manage — for new code, prefer the CDP bridge above.
+
+ Selenium's `debugger_address` only supports local `host:port` connections. For remote CDP over WebSocket, use [Playwright](https://docs.browser-use.com/cloud/browser/playwright) or [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) instead.
+
+ Always stop browser sessions when done. Sessions left running will continue to incur charges until the timeout expires.
+
+## See also
+
+- [Playwright](https://docs.browser-use.com/cloud/browser/playwright) and [Puppeteer](https://docs.browser-use.com/cloud/browser/puppeteer) connections
+- [Live preview & recording](https://docs.browser-use.com/cloud/browser/live-preview) — watch the session or embed it in your app
+- [Proxies](https://docs.browser-use.com/cloud/browser/proxies) and [stealth](https://docs.browser-use.com/cloud/browser/stealth) configuration
+
+
+# Cloud browser + open source agent
+Source: https://docs.browser-use.com/cloud/browser/open-source-agent
+
+
+The [open-source library](/open-source/introduction) runs the agent on your machine. By default it also runs the *browser* on your machine, which means no stealth, no residential proxy, and no CAPTCHA solving. This page connects the two: keep your local agent code, point it at a cloud browser.
+
+## Connect by CDP URL
+
+Create a cloud browser, then pass its CDP URL to the library's `Browser`:
+
+```python
+import asyncio
+from browser_use import Agent, Browser, ChatOpenAI
+from browser_use_sdk.v3 import AsyncBrowserUse
+
+async def main():
+ client = AsyncBrowserUse()
+ cloud_browser = await client.browsers.create(proxy_country_code="us")
+
+ try:
+ agent = Agent(
+ task="Find the current price of iPhone 16 on amazon.de",
+ llm=ChatOpenAI(model="gpt-4o"),
+ browser=Browser(cdp_url=cloud_browser.cdp_url),
+ )
+ await agent.run()
+ finally:
+ await client.browsers.stop(cloud_browser.id)
+
+asyncio.run(main())
+```
+
+The agent behaves exactly as it does locally. The browser it drives is a [stealth Chromium](https://docs.browser-use.com/cloud/browser/stealth) with [CAPTCHA solving](https://docs.browser-use.com/cloud/browser/captcha) and a [residential proxy](https://docs.browser-use.com/cloud/browser/proxies), and you can watch it work through the session's `live_url`.
+
+{/* TEAM REVIEW: confirm the `use_cloud=True` shorthand on Browser() — parameter name, minimum library version, and whether it should be the primary example instead of the cdp_url form. */}
+
+## What you get, what you keep
+
+| | Stays yours | Comes from Cloud |
+|---|---|---|
+| Agent loop, prompts, custom tools | ✓ | |
+| LLM choice and API keys | ✓ | |
+| Browser runtime | | ✓ stealth Chromium |
+| Proxy / IP | | ✓ residential, 195+ countries |
+| CAPTCHA handling | | ✓ automatic |
+| Live view and recording | | ✓ per session |
+
+Billing: only the browser session ($0.02/hour plus proxy data). Your LLM tokens go to your own provider.
+
+## Related
+
+- [Create a browser session](https://docs.browser-use.com/cloud/browser/create) — all session parameters
+- [Open source vs Cloud](https://docs.browser-use.com/cloud/open-source-vs-cloud) — the full decision guide
+- [Manage browser sessions](https://docs.browser-use.com/cloud/browser/sessions) — always stop sessions when done
+
+
+# Profiles
+Source: https://docs.browser-use.com/cloud/guides/authentication
+
+
+Create a profile, then pass its `profile_id` to `run()` — the agent opens a browser seeded from that profile and runs your task on it. Cookies and login state saved during the run persist, so the next run with the same profile is already logged in.
```python Python
from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
+
+# 1. Create a profile (stores cookies + login state across runs)
profile = await client.profiles.create(name="user-id-1")
-# or search existing
+# or reuse an existing one:
# profile = (await client.profiles.list(query="user-id-1")).items[0]
-session = await client.sessions.create(profile_id=profile.id)
-result = await client.run("Check browser-use github stars", session_id=session.id)
-print(result.output)
-# Always stop the session to persist profile state
-await client.sessions.stop(session.id)
+# 2. Run the agent — profile_id attaches a browser seeded from the profile
+result = await client.run(
+ "Go to example.com and return the page title",
+ profile_id=profile.id,
+)
+print(result.output) # -> "Example Domain"
+
+# 3. Reuse the same profile later — saved login/cookies carry over
+followup = await client.run("Check my GitHub notifications", profile_id=profile.id)
```
```typescript TypeScript
import { BrowserUse } from "browser-use-sdk/v3";
const client = new BrowserUse();
+
+// 1. Create a profile (stores cookies + login state across runs)
const profile = await client.profiles.create({ name: "user-id-1" });
-// or search existing
+// or reuse an existing one:
// const profile = (await client.profiles.list({ query: "user-id-1" })).items[0];
-const session = await client.sessions.create({ profileId: profile.id });
-const result = await client.run("Check browser-use github stars", {
- sessionId: session.id,
+
+// 2. Run the agent — profileId attaches a browser seeded from the profile
+const result = await client.run("Go to example.com and return the page title", {
+ profileId: profile.id,
});
-console.log(result.output);
+console.log(result.output); // -> "Example Domain"
-// Always stop the session to persist profile state
-await client.sessions.stop(session.id);
+// 3. Reuse the same profile later — saved login/cookies carry over
+const followup = await client.run("Check my GitHub notifications", { profileId: profile.id });
```
+Passing `profile_id` to `run()` provisions the browser and runs the agent in one call — no separate session step. Profile state is saved automatically when the run ends.
+
View your profile IDs at [cloud.browser-use.com/settings](https://cloud.browser-use.com/settings?tab=profiles).
+## Persist state on a browser you drive (CDP)
+
+Profiles work the same whether the agent drives or you do. Pass `profile_id` to `browsers.create()`, set state over CDP, then **stop the browser to flush cookies and localStorage into the profile**. Reconnect later with the same `profile_id` and the state is there.
+
+```python Python
+from browser_use_sdk.v3 import AsyncBrowserUse
+from playwright.async_api import async_playwright
+
+client = AsyncBrowserUse()
+profile = await client.profiles.create(name="persist-demo")
+
+# Session 1 — write state, then stop to persist
+b1 = await client.browsers.create(profile_id=profile.id)
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(b1.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://en.wikipedia.org")
+ await page.evaluate("localStorage.setItem('demo', 'hello')")
+ await pw.close()
+await client.browsers.stop(b1.id) # flushes state into the profile
+
+# Session 2 — same profile, state is back
+b2 = await client.browsers.create(profile_id=profile.id)
+async with async_playwright() as p:
+ pw = await p.chromium.connect_over_cdp(b2.cdp_url)
+ page = pw.contexts[0].pages[0]
+ await page.goto("https://en.wikipedia.org")
+ value = await page.evaluate("localStorage.getItem('demo')")
+ print(value) # -> hello
+ await pw.close()
+await client.browsers.stop(b2.id)
+```
+```typescript TypeScript
+import { BrowserUse } from "browser-use-sdk/v3";
+import { chromium } from "playwright";
+
+const client = new BrowserUse();
+const profile = await client.profiles.create({ name: "persist-demo" });
+
+// Session 1 — write state, then stop to persist
+const b1 = await client.browsers.create({ profileId: profile.id });
+let pw = await chromium.connectOverCDP(b1.cdpUrl);
+let page = pw.contexts()[0].pages()[0];
+await page.goto("https://en.wikipedia.org");
+await page.evaluate(() => localStorage.setItem("demo", "hello"));
+await pw.close();
+await client.browsers.stop(b1.id); // flushes state into the profile
+
+// Session 2 — same profile, state is back
+const b2 = await client.browsers.create({ profileId: profile.id });
+pw = await chromium.connectOverCDP(b2.cdpUrl);
+page = pw.contexts()[0].pages()[0];
+await page.goto("https://en.wikipedia.org");
+console.log(await page.evaluate(() => localStorage.getItem("demo"))); // -> hello
+await pw.close();
+await client.browsers.stop(b2.id);
+```
+
+ Use a site that actually sets cookies/localStorage to verify persistence — `example.com` sets none, so it is a poor test target.
+
## Manage profiles
```python Python
@@ -1437,7 +2445,7 @@ profile = await client.profiles.create(name="work-account")
# List all
response = await client.profiles.list()
for p in response.items:
-print(p.id, p.name)
+ print(p.id, p.name)
# Search by name
response = await client.profiles.list(query="user-id-1")
@@ -1480,10 +2488,14 @@ await client.profiles.delete(profileId);
- **Per-user profiles:** Create one profile per end-user. Query by name to get the profile ID, or store a mapping between your users and their profile IDs in your database.
- Profile state is only saved when the session ends. Always call `sessions.stop()` when you are done — if a session is left open or times out, changes may not be persisted. Every code path that uses a profile must stop the session, including error handlers.
+ Profile state is saved when the run ends — call `sessions.stop()` (agent) or `browsers.stop()` (CDP) when you are done. Both paths persist; a session left open or timed out may not save. Stop in a `finally` so every code path, including error handlers, persists.
+## Further reading
-# Sync local and cloud cookies
+- [How to authenticate AI web agents](https://browser-use.com/posts/web-agent-authentication)
+
+
+# Profiles / Cookie sync
Source: https://docs.browser-use.com/cloud/guides/profile-sync
@@ -1603,8 +2615,8 @@ print(f"Live view: {session.live_url}")
# Agent navigates to login
result = await client.run(
-"Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA",
-session_id=session.id,
+ "Go to example.com/login and enter username user@example.com and password mypassword, then stop before 2FA",
+ session_id=session.id,
)
# Human completes 2FA in the live view
@@ -1612,8 +2624,8 @@ input("Complete 2FA in the live view, then press Enter...")
# Agent continues
result = await client.run(
-"You are now logged in. Go to the dashboard and export the monthly report",
-session_id=session.id,
+ "You are now logged in. Go to the dashboard and export the monthly report",
+ session_id=session.id,
)
print(result.output)
await client.sessions.stop(session.id)
@@ -1662,14 +2674,14 @@ from browser_use_sdk.v3 import AsyncBrowserUse
client = AsyncBrowserUse()
result = await client.run(
-"""
-1. Go to example.com/signup
-2. Sign up with the agent's email address (use the email available to you)
-3. Check your email inbox for the verification code
-4. Enter the code on the website
-5. Complete the registration
-""",
-agentmail=True, # default, shown for clarity
+ """
+ 1. Go to example.com/signup
+ 2. Sign up with the agent's email address (use the email available to you)
+ 3. Check your email inbox for the verification code
+ 4. Enter the code on the website
+ 5. Complete the registration
+ """,
+ agentmail=True, # default, shown for clarity
)
print(result.output)
```
@@ -1719,16 +2731,16 @@ client = AsyncBrowserUse()
totp_secret = "JBSWY3DPEHPK3PXP"
result = await client.run(
-f"""
-Log into example.com with username user@example.com and password mypassword.
-When prompted for a 2FA code, generate one using pyotp:
+ f"""
+ Log into example.com with username user@example.com and password mypassword.
+ When prompted for a 2FA code, generate one using pyotp:
-import pyotp
-totp = pyotp.TOTP("{totp_secret}")
-code = totp.now()
+ import pyotp
+ totp = pyotp.TOTP("{totp_secret}")
+ code = totp.now()
-Enter the generated code.
-""",
+ Enter the generated code.
+ """,
)
print(result.output)
```
@@ -1772,100 +2784,502 @@ Use **Agent Mail** (enabled by default). For end-client scenarios, have them for
Use **TOTP secret in prompt** — the agent generates codes via pyotp, no human intervention needed.
-# OpenClaw
-Source: https://docs.browser-use.com/cloud/tutorials/integrations/openclaw
+# Webhooks
+Source: https://docs.browser-use.com/cloud/guides/webhooks
-[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.
+Set up webhooks at [cloud.browser-use.com/settings?tab=webhooks](https://cloud.browser-use.com/settings?tab=webhooks).
-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.
+## Events
-## Option 1: Cloud Browser via CDP
+| Event | When |
+|-------|------|
+| `agent.task.status_update` | Task status changes (`running`, `idle`, or `stopped`) |
+| `test` | Webhook test ping |
-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.
+## Payload
-Browser Use exposes a WebSocket CDP URL. OpenClaw connects to it like any remote browser — no SDK or extra dependencies needed.
+```json
+{
+ "type": "agent.task.status_update",
+ "timestamp": "2025-01-15T10:30:00Z",
+ "payload": {
+ "task_id": "task_abc123",
+ "session_id": "session_xyz",
+ "status": "idle",
+ "metadata": {}
+ }
+}
+```
-### Setup
+## Signature verification
-**1. Get your API key**
+Every webhook request includes two headers:
-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).
+- `X-Browser-Use-Signature` — HMAC-SHA256 signature of the payload
+- `X-Browser-Use-Timestamp` — Unix timestamp (seconds) when the request was sent
-**2. Add a Browser Use profile**
+The signature is computed over `{timestamp}.{body}`, where `body` is the JSON-serialized payload with keys sorted alphabetically and no extra whitespace. Verify it to ensure the request is authentic and to prevent replay attacks.
-Open `~/.openclaw/openclaw.json` and add a `browser-use` profile:
+```python Python
+import hashlib
+import hmac
+import json
+import time
-```json5
-{
- browser: {
-enabled: true,
-defaultProfile: "browser-use",
-remoteCdpTimeoutMs: 3000,
-remoteCdpHandshakeTimeoutMs: 5000,
-profiles: {
- "browser-use": {
- cdpUrl: "wss://connect.browser-use.com?apiKey=