Skip to content

Guide

Moving from Browserbase

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. For agents, this guide is also at /migrate-from-browserbase.md.

The change

Create a session
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}}'
Playwright
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

BrowserbaseSteerpane
API keyx-bb-api-key headerAuthorization: Bearer stp_live_…. The token belongs to one organization, so there's no projectId to send. project_id is optional.
Create a sessionPOST /v1/sessionsPOST /v1/sessions, with flat fields instead of browserSettings: viewport, profile_id, keep_alive, record, solve_captchas.
Time limittimeout, in secondstimeout_min, in minutes, capped by your plan. There's also idle_timeout_min, 20 by default.
Connect over CDPconnectUrl, returned ready to usecdp_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 sessionPOST /v1/sessions/{id} with status: REQUEST_RELEASEPOST /v1/sessions/{id}/stop
Saved loginsContexts, passed as browserSettings.contextProfiles, passed as profile_id. A profile has one writer: a second gets 409 profile_locked unless it uses profile_mode: "read_only".
Live viewGET /v1/sessions/{id}/debug, debuggerFullscreenUrlwatch_url is the dashboard page, for people in your organization. For your own users, make an embed link (Scale plan and up).
MetadatauserMetadatametadata, read back from GET /v1/sessions/{id}/meta. Filtering sessions by it isn't supported.
DownloadsGET /v1/sessions/{id}/downloadsThe download action returns a file_id in the file store. POST /v1/sessions/{id}/files/zip gives one ZIP.
UploadsPOST /v1/sessions/{id}/uploadsPOST /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 domainsallowedDomainsallowed_domains
CAPTCHAssolveCaptchas, on by defaultsolve_captchas, off by default and Pro plan and up, or solve_captcha on demand over MCP.
act, observe, extractStagehand, and their MCP serverMCP 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.