When should a Browserbase agent read a page without opening a browser?
Open a Browserbase session when the agent has to click, log in, fill in a form or leave a replay to debug. For a plain read of one page, especially from a chosen country, send it to UpRock instead, because Browserbase bills each session for at least a minute of browser time and a megabyte of proxy traffic, while UpRock charges $0.50 per 1,000 pages. The same fallback catches the moment every Browserbase browser is busy.
Updated · Read as markdown
What Browserbase does well
Browserbase runs headless browsers in the cloud for agents. You connect with Playwright, Puppeteer or Selenium, or use Stagehand, its open-source (MIT) framework that drives a browser from natural-language steps. Every session is recorded for replay, you can watch and take over a live session, contexts keep logins across sessions, downloads are saved for you, CAPTCHAs are solved on paid plans, and managed residential proxies cover more than 200 countries. There's also a hosted MCP server, a Fetch API and a Search API.
When an agent has to act on a page (log in, click through a flow, fill in a form), a browser session is the right tool, and Browserbase is a good one. Keep it.
What 1,000 page reads cost
Many agent steps are just reads: open a page, take the text, move on. A browser session is a lot of machinery for that, and Browserbase's billing reflects it: browser time is billed by the minute and proxy traffic by the megabyte, with a one-minute and one-megabyte minimum per session.
Here's what 1,000 single-page reads cost on the Developer plan ($20 a month) once the included allowance is used up, from Browserbase's published prices on 21 Oct 2026:
| How you read 1,000 pages | Browser time | Proxy traffic (needed for a country) | Total |
|---|---|---|---|
| A new session per page, no country | 1,000 × the 1-minute minimum = 16.7 hours × $0.12 | none | $2.00 |
| A new session per page, from a chosen country | the same 16.7 hours: $2.00 | at least 1 MB per session, 1,000 MB × $12 per GB | at least $14 |
| One long session reused for every page (10 s each) | 2.8 hours × $0.12 = $0.33 | the pages' real size, $12 per GB | $0.33 plus traffic |
| Fetch API, proxies + markdown | none | included | $7.00 |
| UpRock | country included | $0.50 |
On the Startup plan ($99 a month) the overage rates drop to $0.10 per browser hour and $10 per GB, and Fetch calls without extras cost $0.50 per 1,000. Reusing one session is the cheapest Browserbase option, but it means one country, one identity and one point of failure for the whole batch.
When to send a read somewhere else
| What happens on Browserbase | What it means | Send it to UpRock? |
|---|---|---|
| Creating a session returns 429 | Every browser your plan allows is busy, or you hit the session creation rate | Yes, right away |
| The page loads with status 401, 403, 429 or 5xx | The site refused or failed this session | Yes |
TimeoutError while loading | The page didn't load in time | Yes |
ERR_TUNNEL_CONNECTION_FAILED | Browserbase's proxies decline this site's category (banking, government, ticketing, webmail, gambling) | No. Respect it |
| The step needs a click, a login or a form | A read won't do | No. That's Browserbase's job |
Browserbase and UpRock side by side
| Browserbase | UpRock | |
|---|---|---|
| What you get | A full browser session you control | One page per call, as markdown and HTML |
| Price | $20 or $99 a month with 100 or 500 browser hours, then $0.12 or $0.10 per hour; proxies $12 or $10 per GB; Fetch with proxies + markdown $7 per 1,000 | $0.50 per 1,000 pages by card or USDC, $0.40 with UPT |
| Billing minimum | One minute and one MB per session | One credit per page |
| Concurrency | 3, 25 or 100 browsers by plan; over the limit returns 429 | Not published yet |
| Where requests come from | Browserbase's cloud, with managed residential proxies on paid plans | Devices whose owners opted in |
| Countries | 200+, "best effort", city and US state available | 63, or a region |
| Clicks, logins, forms, CAPTCHAs | Yes | No |
| Session replay and live view | Yes | No |
| MCP server | Hosted, with browser actions | Yes, remote (streamable HTTP) |
| Video | Session recordings for debugging, not video search | Search public video by moment, $1 per 1,000 searches |
| To try it | A Free plan with 1 browser hour, no proxies | Prepaid credits |
Where Browserbase is better: anything interactive, from logins to multi-step flows, plus replays, live takeover, CAPTCHA solving and wider country coverage. Where UpRock fits: plain reads, at a fraction of the cost of a session, from a chosen country, and as the place a read goes when every browser is busy.
The fallback, in TypeScript and Python
Every version open a Browserbase session with a residential proxy in the country you ask for, read the page with Playwright, and close the session. If the session can't be created (429 and other API errors), the page answers with a status worth retrying, or the load times out, the read goes to UpRock. SDK retries are switched off so a busy pool falls through at once. UpRock gets one POST /v1/crawl request with your key in an Authorization: Bearer header; the MCP and CLI tabs do the same through UpRock's MCP server (TypeScript) or CLI (Python).
// npm i @browserbasehq/sdk playwright-core (Node 18+)// env: BROWSERBASE_API_KEY, UPROCK_API_KEYimport Browserbase from "@browserbasehq/sdk";import { chromium } from "playwright-core";// maxRetries: 0 so a full concurrency pool (429) falls through to UpRock at once.const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY, maxRetries: 0 });// Target statuses worth a second try from a device in the same country.const RETRY = new Set([401, 403, 429, 500, 502, 503, 504]);async function viaBrowserbase(url: string, country: string) { // Built-in proxies and geolocation need a Developer plan or higher. const session = await bb.sessions.create({ api_timeout: 60, // seconds before Browserbase ends the session proxies: [{ type: "browserbase", geolocation: { country } }], }); const browser = await chromium.connectOverCDP(session.connectUrl); try { const page = browser.contexts()[0].pages()[0]; const res = await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30_000, }); return { status: res?.status() ?? 0, text: await page.innerText("body") }; } finally { await browser.close(); }}async function viaUpRock(url: string, country: string) { // One call: UpRock loads the page on a real device in that country, tries a fresh device // every 7 s (up to 3 tries) and returns markdown. Failed calls aren't billed. const res = await fetch("https://edge.uprock.com/v1/crawl", { method: "POST", headers: { Authorization: `Bearer ${process.env.UPROCK_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ url, country }), signal: AbortSignal.timeout(90_000), }); const page = await res.json(); if (!res.ok) throw new Error(`UpRock ${res.status} ${page.code}: ${page.error}`); return page.markdown ?? "";}export async function read(url: string, country = "US") { try { const page = await viaBrowserbase(url, country); if (!RETRY.has(page.status)) return { via: "browserbase", text: page.text }; console.warn(`The site answered ${page.status} through Browserbase; trying UpRock`); } catch (err) { const message = (err as Error).message; // Browserbase's proxies decline some site categories (banking, government, ticketing, // webmail, gambling). That's a policy answer, not an outage, so don't route around it. if (message.includes("ERR_TUNNEL_CONNECTION_FAILED")) throw err; // 429 (no free browser), timeouts and other navigation errors fall through to UpRock. console.warn(`Browserbase failed (${message}); trying UpRock`); } return { via: "uprock", text: await viaUpRock(url, country) };}// node read.ts https://developer.mozilla.org/en-US/docs/Web/HTTP DEconst [url = "https://developer.mozilla.org/en-US/docs/Web/HTTP", country = "US"] = process.argv.slice(2);const page = await read(url, country);console.log(page.via, page.text.slice(0, 200));# pip install browserbase playwright requests env: BROWSERBASE_API_KEY, UPROCK_API_KEYimport osimport sysimport requestsfrom browserbase import Browserbasefrom playwright.sync_api import sync_playwright# max_retries=0 so a full concurrency pool (429) falls through to UpRock at once.bb = Browserbase(api_key=os.environ.get("BROWSERBASE_API_KEY"), max_retries=0)# Target statuses worth a second try from a device in the same country.RETRY = {401, 403, 429, 500, 502, 503, 504}def via_browserbase(url: str, country: str) -> dict: # Built-in proxies and geolocation need a Developer plan or higher. session = bb.sessions.create( api_timeout=60, proxies=[{"type": "browserbase", "geolocation": {"country": country}}], ) with sync_playwright() as p: browser = p.chromium.connect_over_cdp(session.connect_url) try: page = browser.contexts[0].pages[0] res = page.goto(url, wait_until="domcontentloaded", timeout=30_000) return {"status": res.status if res else 0, "text": page.inner_text("body")} finally: browser.close()def via_uprock(url: str, country: str) -> str: # One call: UpRock loads the page on a real device in that country, tries a fresh device # every 7 s (up to 3 tries) and returns markdown. Failed calls aren't billed. res = requests.post( "https://edge.uprock.com/v1/crawl", headers={"Authorization": f"Bearer {os.environ['UPROCK_API_KEY']}"}, json={"url": url, "country": country}, timeout=90, ) page = res.json() if not res.ok: raise RuntimeError(f"UpRock {res.status_code} {page.get('code')}: {page.get('error')}") return page.get("markdown", "")def read(url: str, country: str = "US") -> dict: try: page = via_browserbase(url, country) if page["status"] not in RETRY: return {"via": "browserbase", "text": page["text"]} print( f"The site answered {page['status']} through Browserbase; trying UpRock", file=sys.stderr, ) except Exception as err: # Browserbase's proxies decline some site categories (banking, government, ticketing, # webmail, gambling). That's a policy answer, not an outage, so don't route around it. if "ERR_TUNNEL_CONNECTION_FAILED" in str(err): raise # 429 (no free browser), timeouts and other navigation errors fall through to UpRock. print(f"Browserbase failed ({err}); trying UpRock", file=sys.stderr) return {"via": "uprock", "text": via_uprock(url, country)}if __name__ == "__main__": # python read.py https://developer.mozilla.org/en-US/docs/Web/HTTP DE url = sys.argv[1] if len(sys.argv) > 1 else "https://developer.mozilla.org/en-US/docs/Web/HTTP" country = sys.argv[2] if len(sys.argv) > 2 else "US" page = read(url, country) print(page["via"], page["text"][:200])// npm i @browserbasehq/sdk playwright-core @modelcontextprotocol/sdk (Node 18+)// env: BROWSERBASE_API_KEY, UPROCK_API_KEYimport Browserbase from "@browserbasehq/sdk";import { chromium } from "playwright-core";import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";// maxRetries: 0 so a full concurrency pool (429) falls through to UpRock at once.const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY, maxRetries: 0 });const uprock = new Client({ name: "browserbase-fallback", version: "1.0.0" });await uprock.connect( new StreamableHTTPClientTransport( new URL("https://mcp.uprock.ai/mcp"), { requestInit: { headers: { Authorization: `Bearer ${process.env.UPROCK_API_KEY}` } } }, ),);// Target statuses worth a second try from a device in the same country.const RETRY = new Set([401, 403, 429, 500, 502, 503, 504]);const textOf = (content: unknown) => (content as { type: string; text?: string }[]).find((c) => c.type === "text")?.text ?? "";async function viaBrowserbase(url: string, country: string) { // Built-in proxies and geolocation need a Developer plan or higher. const session = await bb.sessions.create({ api_timeout: 60, // seconds before Browserbase ends the session proxies: [{ type: "browserbase", geolocation: { country } }], }); const browser = await chromium.connectOverCDP(session.connectUrl); try { const page = browser.contexts()[0].pages()[0]; const res = await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30_000, }); return { status: res?.status() ?? 0, text: await page.innerText("body") }; } finally { await browser.close(); }}async function viaUpRock(url: string, country: string) { const res = await uprock.callTool( { name: "crawl_fetch", arguments: { url, country } }, undefined, { timeout: 300_000 }, ); if (res.isError) throw new Error(`UpRock: ${textOf(res.content)}`); const uri = JSON.parse(textOf(res.content)).content?.markdown?.resource; if (!uri) throw new Error(`UpRock: no markdown for ${url}`); return textOf( (await uprock.callTool({ name: "resource_fetch", arguments: { uri } })).content, );}export async function read(url: string, country = "US") { try { const page = await viaBrowserbase(url, country); if (!RETRY.has(page.status)) return { via: "browserbase", text: page.text }; console.warn(`The site answered ${page.status} through Browserbase; trying UpRock`); } catch (err) { const message = (err as Error).message; // Browserbase's proxies decline some site categories (banking, government, ticketing, // webmail, gambling). That's a policy answer, not an outage, so don't route around it. if (message.includes("ERR_TUNNEL_CONNECTION_FAILED")) throw err; // 429 (no free browser), timeouts and other navigation errors fall through to UpRock. console.warn(`Browserbase failed (${message}); trying UpRock`); } return { via: "uprock", text: await viaUpRock(url, country) };}// node read.ts https://developer.mozilla.org/en-US/docs/Web/HTTP DEconst [url = "https://developer.mozilla.org/en-US/docs/Web/HTTP", country = "US"] = process.argv.slice(2);try { const page = await read(url, country); console.log(page.via, page.text.slice(0, 200));} finally { await uprock.close();}# pip install browserbase playwright; npm i -g uprock env: BROWSERBASE_API_KEY, UPROCK_API_KEYimport jsonimport osimport subprocessimport sysfrom browserbase import Browserbasefrom playwright.sync_api import sync_playwright# max_retries=0 so a full concurrency pool (429) falls through to UpRock at once.bb = Browserbase(api_key=os.environ.get("BROWSERBASE_API_KEY"), max_retries=0)# Target statuses worth a second try from a device in the same country.RETRY = {401, 403, 429, 500, 502, 503, 504}def via_browserbase(url: str, country: str) -> dict: # Built-in proxies and geolocation need a Developer plan or higher. session = bb.sessions.create( api_timeout=60, proxies=[{"type": "browserbase", "geolocation": {"country": country}}], ) with sync_playwright() as p: browser = p.chromium.connect_over_cdp(session.connect_url) try: page = browser.contexts[0].pages[0] res = page.goto(url, wait_until="domcontentloaded", timeout=30_000) return {"status": res.status if res else 0, "text": page.inner_text("body")} finally: browser.close()def via_uprock(url: str, country: str) -> str: run = subprocess.run( ["uprock", "ai", "crawl", url, "--country", country, "--content"], capture_output=True, text=True, timeout=330, ) if run.returncode != 0: raise RuntimeError(f"UpRock: {run.stderr.strip()}") return json.loads(run.stdout).get("inlined_markdown", "")def read(url: str, country: str = "US") -> dict: try: page = via_browserbase(url, country) if page["status"] not in RETRY: return {"via": "browserbase", "text": page["text"]} print( f"The site answered {page['status']} through Browserbase; trying UpRock", file=sys.stderr, ) except Exception as err: # Browserbase's proxies decline some site categories (banking, government, ticketing, # webmail, gambling). That's a policy answer, not an outage, so don't route around it. if "ERR_TUNNEL_CONNECTION_FAILED" in str(err): raise # 429 (no free browser), timeouts and other navigation errors fall through to UpRock. print(f"Browserbase failed ({err}); trying UpRock", file=sys.stderr) return {"via": "uprock", "text": via_uprock(url, country)}if __name__ == "__main__": # python read.py https://developer.mozilla.org/en-US/docs/Web/HTTP DE url = sys.argv[1] if len(sys.argv) > 1 else "https://developer.mozilla.org/en-US/docs/Web/HTTP" country = sys.argv[2] if len(sys.argv) > 2 else "US" page = read(url, country) print(page["via"], page["text"][:200])Tested 21 Oct 2026 with an invalid Browserbase key, which fails session creation and sends the read to UpRock. Without a paid plan, the session path follows Browserbase's quickstart and SDK types (Node 2.22, Python 1.21). On 6 Nov 2026 we ran the HTTP and MCP versions again with an UpRock API key in an Authorization header: with an invalid Browserbase key, each fell back to UpRock and got the page.
FAQ
Why does a single page cost a full minute on Browserbase?
Browserbase's billing docs say browser time is billed by the minute and proxy bandwidth by the megabyte, with a one-minute and one-megabyte minimum per session. A read that takes ten seconds in its own session is billed as a minute.
What happens when all my Browserbase browsers are in use?
Creating another session returns HTTP 429. The Node and Python SDKs retry 429s twice by default; the code on this page turns that off so the read goes to UpRock at once instead of waiting.
Isn't Browserbase's Fetch API the same thing as UpRock?
It's the closest match. Fetch with proxies and markdown output (Browserbase calls markdown "Fetch Extract") costs $7 per 1,000 calls on the paid plans. Browserbase's docs say Fetch doesn't render JavaScript and caps pages at 5 MB, though its product page says it does render. UpRock loads every page in a real browser.
Should I send ERR_TUNNEL_CONNECTION_FAILED pages to UpRock?
No. Browserbase's proxies decline some site categories, such as banking, government, ticketing, webmail and gambling. That's a policy decision about the site, and the fallback on this page respects it.
Can UpRock click or log in?
No. UpRock loads a page and returns it; it doesn't keep a session, click or sign in. For that, Browserbase (or Stagehand on top of it) is the right tool.
Other use cases to explore
- Cap what a single page can cost: one provider's credit table, from 1 to 75 credits a page, and how to stop before the expensive tiers.
- See a page as it looks in another country: one store page from five countries, with five prices, two languages and a cookie banner in one country only.
- Estimate what 1,000 pages will cost: token counts for six real pages, and the length where a flat per-page price is cheaper.
- Search public video by moment: find the second something is shown or said, across platforms.
References
- Browserbase, Pricing, checked 21 Oct 2026
- Browserbase docs, Plans and billing minimums, checked 21 Oct 2026
- Browserbase docs, Proxies (geolocation, excluded site categories), checked 21 Oct 2026
- Browserbase docs, Fetch API (Fetch vs Fetch Extract), checked 21 Oct 2026
- Browserbase docs, Concurrency and rate limits, checked 21 Oct 2026
- Browserbase docs, Create a session (API reference), checked 21 Oct 2026
- Browserbase docs, Session replay, checked 21 Oct 2026
- Browserbase docs, MCP server setup, checked 21 Oct 2026
- Stagehand on GitHub (MIT), checked 21 Oct 2026
- UpRock, everything in one file (API, CLI, MCP, pricing), checked 21 Oct 2026
Get started
Add UpRock in one paste.
Paste this into Claude Code, Codex, Cursor or any agent with a terminal. It reads our docs, sets up the MCP server or CLI, and tells you the cost before it spends anything.
Set up UpRock for me: read https://uprock.ai/agents.md, connect the UpRock MCP server, and run one test crawl. Tell me what it costs before you spend anything.