act / extract / observe verbs let you script the
page in natural language instead of brittle selectors. It does that job well, and the API is
mature. But it is also a separate vendor account:
- The session lives behind a
BROWSERBASE_API_KEY+ project ID, its own console, and its own billing — disconnected from wherever the agent’s email, cards, secrets, and KYC live. - The interesting workflows — sign this agent up for a SaaS, then log it back in later — are yours to build: you generate the password, you decide where to store it, and you own the risk of it leaking. Browserbase persists cookies via Contexts; it does not vault the credential itself.
- “Who let this agent create an account, and what else can it touch?” is answered in Browserbase for browser sessions, and in unrelated systems for everything else. The session has no shared accountability with the rest of the agent’s footprint.
browser primitive gives the agent the same capability —
a cloud browser it drives with navigate / act / extract / observe — but the session is
rooted in one governed identity:
- The tenant user that owns the session is the same user that owns its email inboxes, its cards, its vault secrets, and its KYC.
- Autonomous
signupgenerates a strong password, fills the form, and vaults{ email, password }server-side under that identity — the password never reaches your code or the model. A laterloginreads it back the same way. - Whether an agent may browse at all — which domains, whether it can submit forms, whether creating an account freezes for human approval — is decided by that user’s Account Kit and the session’s allowlist at execution time.
Browserbase and Stagehand are trademarks of their respective owners, used here for identification only. No endorsement or affiliation is implied.
Tested against: the Browserbase Node SDK
@browserbasehq/sdk
v2.x (bb.sessions.create, bb.contexts.create, base https://api.browserbase.com) and
Stagehand @browserbasehq/stagehand
v3 (stagehand.act / extract / observe on the instance; docs snapshot June 2026), and the
Naive Node SDK @usenaive-sdk/server against the Naive API (base
https://api.usenaive.ai/v1, docs snapshot June 2026).Version notes:- Stagehand v3 moved
act/extract/observefrom thepageobject to thestagehandinstance and unified model config into a singlemodeloption. v2 calledpage.act({ action }). This guide uses the v3 shape. - Browserbase separates concerns: the SDK opens the session (
sessions.create→connectUrl), you connect with Playwright/Puppeteer over CDP, and Stagehand layers the NL verbs on top. Naive folds open + drive + autonomous signup/login into one per-user primitive with no CDP/Playwright wiring. - Browserbase identifies a session by its
idand persists login state via the Contexts API. Naive returns asession_id, enforces a domain allowlist (default-deny), and persists logins as human-granted saved contexts — an agent can use one but generally cannot create the grant itself. - Naive sessions have a hard TTL (default 15 min, max 30) and a per-session credit ceiling of
40. Browserbase supports much longer sessions (up to 6h) and
keepAlive— see what doesn’t map yet.
Concept map
| Browserbase / Stagehand | Naive | Notes |
|---|---|---|
new Browserbase({ apiKey }) + new Stagehand({ env, apiKey, model }) | new Naive({ apiKey }) | Server-side credential in both cases |
BROWSERBASE_API_KEY + project ID — one Browserbase account, isolated from your other vendors | tenant user via naive.forUser(id) — scopes every primitive | The core consolidation win |
| Project as the unit of grouping (browser sessions only) | Tenant users (naive.forUser(id)) | Naive’s unit of isolation spans every primitive, not just the browser |
bb.sessions.create({ projectId, browserSettings }) → connectUrl | client.browser.createSession({ allowed_domains, allow_writes, timeout }) → session_id | Naive sessions are domain-allowlisted (default-deny) and per-user |
chromium.connectOverCDP(session.connectUrl) + Playwright page | (none) | No CDP/Playwright wiring — you call verbs directly on the session_id |
await stagehand.init() (launches the model + browser) | (folded into createSession) | No separate model key — Naive runs the automation model server-side |
page.goto(url) | client.browser.navigate(sessionId, url) | Allowlist + SSRF denylist enforced on every navigation |
stagehand.act("click …") | client.browser.act(sessionId, "click …") | Natural-language action; submits/destructive actions need allow_writes |
stagehand.extract(instruction, zodSchema) | client.browser.extract(sessionId, instruction) | Read-only; no schema arg — see gaps |
stagehand.observe("find …") | client.browser.observe(sessionId, "find …") | List candidate elements/actions (read-only) |
page.screenshot() | client.browser.screenshot(sessionId) | Short-lived signed URL |
stagehand.close() / session timeout | client.browser.closeSession(sessionId) | Always close — idle sessions bill a vendor time floor at close |
bb.sessions.list() | client.browser.listSessions() | Sessions for the identity |
Build it yourself: act + your own password + your own secret store | client.browser.signup({ service, url }) | Generates a strong password, fills the form, vaults the credential server-side |
| Re-use a Context to land logged-in | client.browser.login({ service, url }) | Reads the vaulted credential and re-authenticates — password never returned |
act("…", { variables: { password } }) — secret substituted, but you supply + store it | %password% substituted server-side — you never hold it | Secret never crosses the agent/LLM boundary in either case; ownership differs |
bb.contexts.create() + browserSettings.context: { id, persist } | Saved logins: --human-login → context save → context grant | Persistence is human-granted, default-deny (an agent can use, never grant) |
| Manual login via Session Live View, then persist context | Dashboard Browser tab → Live View → Take control | One-time human login for SSO / 2FA / CAPTCHA |
bb.sessions.debug(id) → live/debugger URL | Dashboard Live View (watch + take control) | Human watch/intervene |
proxies: true / geolocation | --proxy residential proxy (requires an allowlist) | |
| API-key scopes / project separation | Account Kit browser enabled + per-kit requiresApproval for signup | Permission is execution-time policy on the identity — see gains |
browserSettings.solveCaptchas | — | No automatic CAPTCHA solving — a human takes control in Live View |
stagehand.agent({...}).execute(...) (CUA multi-step loop) | — | No built-in autonomous multi-step agent — see gaps |
| Bring-your-own model / per-call model choice | — | Automation model is server-side; not selectable |
| Session recording / replay, downloads, file uploads, extensions | — | Not provided — see gaps |
timeout up to 6h, keepAlive | Hard TTL 15 min (max 30), ceiling 40 credits/session | Re-open sessions; persist with saved logins |
Before / after: the core path
The path that matters for almost every browsing agent is open a session, go somewhere, do something, read something back. Here it is on both platforms.- One handle, no plumbing. Browserbase is create session → connect over CDP → wrap with
Stagehand → bring your own model key. Naive’s
createSessionreturns asession_idyou drive directly — no Playwright, no CDP, no separate model credential. - Sessions are allowlisted by default. Every Naive session must pass
allowed_domains(default-deny); navigation also runs through an SSRF denylist. Pass['*']to browse unrestricted (not recommended). - Writes are gated. A destructive/submit
actis refused unless the session was opened withallow_writes: true. Reading and clicking links work without it. extractis instruction-only. Naive returns extracted data from a natural-language instruction; it does not take a Zod/JSON schema. Validate the shape in your own code — see gaps.- The id is your identity, not a separate account. In Browserbase the API key scopes
Browserbase. In Naive the same
forUser(id)handle also owns the agent’s email, cards, vault, and KYC.
The workflow that actually changes: signup & login
This is where the migration earns its keep. On Browserbase you build account creation: generate a password, drive the form, and decide where to keep the secret. Stagehand can substitute the value so it never hits the model — but you still own it. Naive turns the whole flow into one call that vaults the credential under the user’s identity.- Credential custody flips. Browserbase substitutes the secret at fill-time but leaves you holding it. Naive generates and vaults it under the tenant user — the agent and the model never see the password.
- Verification emails land in the same identity.
signupuses the user’sprofileemail as the account email. Point it at a provisioned inbox (client.profile.setEmail(...)) so confirmation links arrive in a mailbox the same user owns. - Account creation is approval-gated. Because
signupcreates a real account under the user’s identity, it freezes for human approval by default — toggle per Account Kit.
Minimal viable migration
The smallest swap that keeps a working browsing agent running is just open → drive → read → close.Install the SDK and set your key
NAIVE_API_KEY (a server-side key from the dashboard).
You can drop BROWSERBASE_API_KEY, the project ID, and the separate model key for this path.Swap session creation
Replace
new Stagehand({...}) + stagehand.init() (or bb.sessions.create() +
chromium.connectOverCDP(...)) with a single
client.browser.createSession({ allowed_domains: [...] }). Keep the returned session_id.
Add allow_writes: true only if the agent submits forms.Swap the drive verbs
Map each call onto the
session_id:
page.goto → client.browser.navigate(sessionId, url),
stagehand.act → client.browser.act(sessionId, instruction),
stagehand.extract → client.browser.extract(sessionId, instruction) (drop the schema),
stagehand.observe → client.browser.observe(sessionId, instruction),
page.screenshot → client.browser.screenshot(sessionId).Swap teardown
Replace
stagehand.close() with client.browser.closeSession(sessionId). Always close —
an idle session bills a vendor time floor at close (1 credit ≤5 min, 2 ≤15 min, 3 ≤30 min).Consolidate further once you’re on Naive
This is where the migration pays for itself. In Browserbase, the API key isolates browser sessions and nothing else, and any account the agent creates is a credential you babysit. On Naive, the unit of isolation is a tenant user, and the browser is one of many primitives that identity owns.Gain #1 — one identity across primitives
- With Browserbase, the agent’s sessions are an island behind an API key, and any account it
creates is a secret you store and protect yourself. With Naive,
naive.forUser(acme.id)is a single handle to browser and email and cards and vault and KYC. - The SaaS accounts the agent signs up for are not loose credentials — they are vaulted under the identity that owns everything else. Tear the customer down from one place; sibling tenants can never read each other’s sessions, logins, cards, or secrets.
Gain #2 — execution-time permission enforcement
- Whether an agent may browse at all, which domains it may reach, whether it may submit forms, and whether creating an account freezes for human review are all policy on the identity — not checks you hand-write and not API-key scopes you manage in a second console.
- The agent’s code is identical for every tier —
client.browser.act(...),client.browser.signup(...). Whether the call runs is decided at execution time:- A session must pass its
allowed_domainsallowlist (default-deny) and an SSRF denylist — no code change on your side. - A destructive
actwithoutallow_writesis refused. - With
browser.requiresApproval: true, an autonomoussignupreturnspending_approvaland runs only after a human approves it. Driving the page is a routine action and is not approval-gated. - Saved logins are human-granted. An agent can use a saved login but generally cannot create the grant or revoke a shared one — that lives in the dashboard.
- A session must pass its
Gain #3 — unified accountability
- Every session, navigation, action, and autonomous signup for a customer lands in one per-user activity log — alongside their email, card, vault, and KYC events, not in a separate Browserbase console:
- That is the question that is hard to answer when browsing lives in Browserbase, the SaaS password lives in your secrets manager, cards live in Stripe, and email lives somewhere else. Under Naive it is a single query.
What does not map yet
A migration guide that hides gaps is worse than none. The core path (open → navigate → act → extract → close) and the signup/login flow map cleanly, but the following Browserbase / Stagehand capabilities have no direct equivalent on Naive’s browser primitive today. Check this list against your app before you commit.| Browserbase / Stagehand feature | Status on Naive | Workaround |
|---|---|---|
Raw CDP / Playwright / Puppeteer access (connectUrl) | Not provided — driven via SDK verbs only | Use navigate / act / extract / observe; no arbitrary Playwright calls |
Stagehand agent() autonomous multi-step loop (Computer-Use Agent) | Not provided | Orchestrate the verbs yourself, or drive them from orchestration |
| Bring-your-own model / per-call model choice | Not exposed — the automation model runs server-side | No model selection in the browser verbs |
extract with a Zod/JSON schema | Instruction-only extract | Parse + validate the returned data in your own code |
Auto CAPTCHA solving (solveCaptchas) | Not automatic | A human clicks Take control in the dashboard Live View |
| Session recording / replay, downloads, file uploads, browser extensions | Not provided | Screenshot per step; no replay / upload / extension APIs |
Long-lived sessions (timeout up to 6h, keepAlive) | Hard TTL 15 min (max 30), 40-credit ceiling/session | Re-open sessions; persist state via saved logins |
| Self-managed Contexts (create/persist arbitrary cookie state) | Saved logins are human-granted, default-deny | A human opens a --human-login session, then context save + context grant |
Custom fingerprints / verified OS spoofing / viewport / regions | Not exposed | Residential --proxy only; limited fingerprint control |
userMetadata and other session config | Not exposed | Track your own metadata against the session_id |
Where to go next
browserprimitive — full session lifecycle, autonomous signup/login, saved logins, live viewbrowserSDK sub-client — typed method signaturesbrowserCLI — session create/navigate/act/extract, signup/login, context save/grant- Vault — where autonomous signup stores
login:<service>credentials - Account Kits and Approvals — the policy model behind execution-time governance and signup approval
- Email and Profile — the inbox that receives the agent’s account-verification mail
- Tenant users — the identity that owns the browser, email, cards, and vault