# Moving from Browserbase to Steerpane

Your Playwright and Puppeteer code keeps working. You change how a session is created and connected, and what ends it. Code built on Browserbase's SDKs needs rewriting against REST or MCP, and we haven't tried Stagehand.

Browserbase details are from its documentation on 2026-11-04. Check its current docs for anything that matters to you.

## The change

```bash
curl -X POST https://api.steerpane.app/v1/sessions \
  -H "Authorization: Bearer stp_live_YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{"initial_url": "https://example.com", "timeout_min": 15, "viewport": {"width": 1280, "height": 800}}'
```

```python
import time, requests
from playwright.sync_api import sync_playwright

API, TOKEN = "https://api.steerpane.app", "stp_live_YOUR_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

session = requests.post(f"{API}/v1/sessions", headers=HEADERS, json={"initial_url": "https://example.com"}).json()
while session["status"] == "starting":  # the session isn't ready as soon as it's created
    time.sleep(0.5)
    session = requests.get(f"{API}/v1/sessions/{session['id']}", headers=HEADERS).json()

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(session["cdp_url"], headers=HEADERS)  # your Playwright code from here on is unchanged
    page = browser.contexts[0].pages[0]
    print(page.title())
    browser.close()

requests.post(f"{API}/v1/sessions/{session['id']}/stop", headers=HEADERS)  # closing the connection doesn't end the session
```

## How the API maps

| | Browserbase | Steerpane |
|---|---|---|
| API key | `x-bb-api-key` header | `Authorization: Bearer stp_live_…`. The token belongs to one organization, so there's no `projectId` to send. `project_id` is optional. |
| Create a session | `POST /v1/sessions` | `POST /v1/sessions`, with flat fields instead of `browserSettings`: `viewport`, `profile_id`, `keep_alive`, `record`, `solve_captchas`. |
| Time limit | `timeout`, in seconds | `timeout_min`, in minutes, capped by your plan. There's also `idle_timeout_min`, 20 by default. |
| Connect over CDP | `connectUrl`, returned ready to use | `cdp_url`. Send the token as an `Authorization` header, or append `?access_token=`. It's a separate step, and the session may still be `starting`: poll `GET /v1/sessions/{id}` first. |
| End a session | `POST /v1/sessions/{id}` with `status: REQUEST_RELEASE` | `POST /v1/sessions/{id}/stop` |
| Saved logins | Contexts, passed as `browserSettings.context` | Profiles, passed as `profile_id`. A profile has one writer: a second gets `409 profile_locked` unless it uses `profile_mode: "read_only"`. |
| Live view | `GET /v1/sessions/{id}/debug`, `debuggerFullscreenUrl` | `watch_url` is the dashboard page, for people in your organization. For your own users, make an embed link (Scale plan and up). |
| Metadata | `userMetadata` | `metadata`, read back from `GET /v1/sessions/{id}/meta`. Filtering sessions by it isn't supported. |
| Downloads | `GET /v1/sessions/{id}/downloads` | The `download` action returns a `file_id` in the file store. `POST /v1/sessions/{id}/files/zip` gives one ZIP. |
| Uploads | `POST /v1/sessions/{id}/uploads` | `POST /v1/files` for a file id, then the `upload_file` action. |
| Fetch and search | `/v1/fetch`, `/v1/search` | `/v1/fetch`, `/v1/search`. Fields differ: `format` is `markdown`, `html` or `raw`, and `mode` is `fast`, `deep` or `auto`. |
| Allowed domains | `allowedDomains` | `allowed_domains` |
| CAPTCHAs | `solveCaptchas`, on by default | `solve_captchas`, off by default and Pro plan and up, or `solve_captcha` on demand over MCP. |
| `act`, `observe`, `extract` | Stagehand, and their MCP server | MCP tools `act`, `observe` and `extract`, using your own model key. |

## What behaves differently

- Closing a CDP connection doesn't end a session. It keeps running until you stop it or it goes idle for `idle_timeout_min`, so stop sessions you're done with, or create them with `auto_close: true`.
- Native dialogs are dismissed by default. Set `dialog_policy: "accept"` if your code relies on confirming them.
- A person can take over at any time. While they do, input and navigation over CDP are held until they hand back, and REST actions return `409 human_in_control`. Your code should expect a wait.
- Errors share one shape: `{"error": {"code", "message", "details"}}`. Branch on `code`.

## What Steerpane doesn't have

- **Selenium.** Only Playwright, Puppeteer and other CDP clients.
- **Browserbase's SDKs.** Use REST, MCP or CDP. We haven't tried the Stagehand library against a Steerpane `cdp_url`, so don't count on it.
- **Scale.** Browsers are limited to 15 at once on the Scale plan, and all run on one node today. Check the pricing page against your concurrency before moving a large workload.
- **Regions.** Sessions run in the regions listed by `GET /v1/platform/public`, and today that's one.
- **Managed proxies.** There's no vendor behind them yet. Bring your own HTTP or SOCKS5 proxy.
- **A hardened browser with partner recognition.** `stealth` makes the user agent and client hints consistent. It isn't Browserbase's Verified browser.
- **HLS replay.** Recordings are frames and events.
- **A model gateway.** Agent runs, `act`, `observe` and `extract` use your own model key.
- **Ready-made framework integrations.** Anything that speaks MCP can connect, but there are no dedicated packages.
- **Compliance reports.** No SOC 2 or HIPAA.

## What you gain

- **Takeover with a queue.** A person takes over and the agent's commands wait. The agent then gets `done` or `not_done` and a note from `wait_for_human`.
- **Takeover inside your product,** with `postMessage` events and per-user links (Scale plan and up).
- **Login codes without a person.** `wait_for_code` types email or SMS codes, and `vault_fill` types passwords and TOTP codes, without the value reaching your model. Both check the page's site first.
- **Profile locks,** so parallel agents can't overwrite a login.
- **One-call tools with no session:** `screenshot_url`, `pdf_url`, `map_site`, `crawl_site` with broken link checks, and `extract_url`.
