# BUGBOT
Source: https://docs.browserbase.com/.cursor/BUGBOT
# Docs writing and positioning guide
**Severity: blocking.** Every rule in this file is mandatory. Flag violations as bugs, not suggestions. Do not approve a PR that violates any of these rules.
## Positioning
Browserbase docs are a demand-capture surface. They may use a customer's problem language when needed for discovery or clarity, but they must always resolve that problem using Browserbase's approved product language.
* **Ladder to the roof.** Every page must support the core narrative: "The web wasn't built for agents. Browserbase is." Position Browserbase in browser agents, not the entire agents category.
* **Lead with one pillar.** For developers, lead with browsers that work where APIs cannot. For leadership, lead with one platform instead of five vendors. At most one other pillar may support the lead.
* **Keep browsers at the center.** Do not position Browserbase as generic computer-use software or commodity infrastructure. Do not use roads, pipes, raw compute, or similar metaphors that reduce Browserbase to price per session.
* **Use only approved proof.** Do not invent statistics, customer names, performance claims, or superlatives. The approved proof points are 35m+ browser sessions per month; 10,000 customers; Ramp, Microsoft, and Lovable; SOC-2 Type II; Stagehand up to 2x faster with approximately 30% lower cost on repeated actions; and Functions with under 5ms latency to the browser.
* **Use the 15/85 or 25/75 API statistic only as an illustrative hook.** Never use it as a headline or as a claim Browserbase must defend. For leadership audiences, prefer the trend framing that 80% of an agent's context lives on the web.
* **Never name competitors in public copy.** This includes comparison copy unless PMM has explicitly reviewed it. Do not name Firecrawl, Browser Use, Kernel, HyperBrowser, Steel, Browserless, Anchor, Parallel, Exa, or Tavily.
* **Do not oversell agent intelligence.** Browserbase promises browsers, identity, observability, and infrastructure, not smarter agents.
## Writing style
These rules are non-negotiable. Flag every violation, even minor ones.
* **Brevity is mandatory.** Every sentence must earn its place. Cut filler words ("just," "simply," "basically," "actually," "in order to," "it should be noted that"). If a sentence can be shorter without losing meaning, it must be.
* **Sentence case for all titles and headings.** Only capitalize the first word and proper nouns (e.g., "How Browserbase took over the world" not "How Browserbase Took Over the World"). Flag any title-cased heading as a bug.
* **Conversational tone, not casual.** Text should sound natural if spoken aloud. Contractions are encouraged ("you'll," "it's," "don't"). No slang, no filler phrases like "let's dive in" or "as you can see."
* **Sound like an engineer stating a fact.** Lead with the outcome, prefer explicit language over clever phrasing, and avoid keynote language such as "revolutionizing how AI interacts with the digital world."
* **Active voice only.** Flag any use of passive voice (e.g., "the session is created" should be "Browserbase creates the session" or "you create the session").
* **Second person ("you") for the reader.** Always address the reader as "you," never as "we." Flag any use of "we" to mean the reader.
* **Oxford comma required.** Always use a comma before "and" or "or" in a list of three or more items. Flag missing oxford commas.
* **Capitalize product names** (Browserbase, Stagehand, Playwright, Functions, Search, Identity, etc.) but default to lowercase for everything else. Flag inconsistent capitalization.
* **No em dashes.** Flag every U+2014 em dash character in docs copy, frontmatter, tables, and code comments as blocking. Use judgment to recommend the clearest rewrite with a period, colon, comma, parentheses, or a rephrased sentence. Plain hyphens are allowed when they are semantically correct, such as compounds, ranges, product names, and command flags. Do not recommend replacing an em dash with a hyphen that still acts like an em dash.
* **Exception:** em dashes are allowed inside fenced example content that readers copy verbatim: system prompts, model prompts, sample task strings, and pasted config/log snippets. The Agent copies these into its own runtime, so preserve authorial voice.
* **`Agent` casing.** Capitalize `Agent` and `Agents` when the word refers to the Browserbase product/feature or a concrete instance of it. Lowercase only when discussing agents as a generic industry concept. Flag mismatches.
* Write "Create an Agent," "your first Agent run," "the Agent's goal," "Job Finder Agent," "an Agent renders each page."
* Write "browser agents adapt to sites that change" or "the SDK for browser agents" (generic category).
* API endpoint names track the OpenAPI spec: "Run an Agent," "Create an Agent," "List Agents."
* **No secrets in example prompts.** Never embed credentials, API keys, tokens, or personal data directly in example prompts, task strings, or code samples. Use `%variableName%` placeholders (Agent variables) or environment variables (`$BROWSERBASE_API_KEY`). Flag inline credentials as blocking.
* **No placeholder or dead links.** Flag any `](#)` or obviously broken href as blocking. Use canonical paths for repeated destinations:
* Stagehand: `/welcome/quickstarts/stagehand`
* Dashboard: `https://www.browserbase.com/overview`
* Agents overview: `/platform/agents/overview`
* Agents API endpoints under `/reference/api/...`.
* **Never use "we," "us," or "our" for Browserbase.** Always refer to the company by name. This applies to first-person plural in any form: subject ("we"), object ("us"), possessive ("our"), and contractions ("we're," "we've," "we'll"). Flag every instance.
* Write "Browserbase recommends" not "we recommend."
* Write "Browserbase provides" not "we provide."
* Write "Browserbase's infrastructure" not "our infrastructure."
* Write "Browserbase supports both transports" not "we support both transports."
* Exception: direct quotes from customers or team members in case studies.
## Tagline
* Current: "Give your agents access to the whole web."
* Deprecated (flag if seen): "Agents can now browse and interact with the web like humans."
* Deprecated (flag if seen): "Autonomously read, write, and perform tasks on the web with a headless browser."
## Structure and formatting
* Make content easy to skim: use bullet points, break up text, include images/videos/tables where helpful.
* Headlines should outline a clear structure and make sense on their own.
* Lead with the main information. Get to the point fast, add details later. Front-load keywords for scanning.
* Make customer choices and next steps obvious.
## Code examples
* Default to SDK code examples over raw API calls whenever possible.
* Label every Node.js code block as `Node.js`, including snippets written in TypeScript or JavaScript. Keep the syntax language for highlighting, then add the visible label, such as `typescript Node.js` or `javascript Node.js`. Never present these snippets as `TypeScript`, `TS`, `JavaScript`, or `JS`.
* Define OpenAPI `x-codeSamples` in the owning API route schema under `apps/api/src/routes/`. Never edit generated code samples only in `apps/docs/reference/api/openapi.v1.yaml`; `pnpm run gen-swagger` overwrites that file. After changing route schemas, run `pnpm run gen-swagger` from `apps/api/`, and commit the generated spec.
* Highlight the pieces that are changing and link out to related docs.
## SEO
* Write with new users, search terms, and AI consumption in mind.
## Terminology
Use these terms consistently:
| Use this | Not this |
| ------------------------------------------------------------------------- | --------------------------------- |
| browser agent | web agent |
| agents | AI (in most external copy) |
| headless browsers | serverless browsers |
| Verified, for the customer-facing access feature | Stealth Mode or Agent Identity |
| Agent Identity, for Web Bot Auth, Signed Agents, and agent-owned accounts | Verified or stealth |
| SDK for browser agents (Stagehand) | browser automation framework |
| agents | agentic workflows |
| browser agent platform (Browserbase) | browser automation infrastructure |
* When listing platform primitives, always put browsers first.
* Capitalize `Browser Agent` and `Browser Agent Platform` when naming the category. Never use `Web Agent`.
* Use `agents` in headings and body copy. `AI Agent` and `AI Agents` are allowed only in SEO or tab titles and on first-touch surfaces for non-AI-native readers.
* Don't lead with Search or Fetch as standalone products; browsers are the core.
* Do not blur Agents and Stagehand. Agents is the managed API product. Stagehand is the SDK and AI browser driver.
* Never include Director in product lists or copy. Director is sunset.
* `Automation` is allowed when it describes the user's outcome, speaks to legacy automation or RPA users, or matches demand-capture search terms.
* `Scraping`, `scraper`, `crawler`, and related terms require case-by-case PMM review. They may match a customer's use-case language, but must not appear in an H1, page title, or sidebar title.
* Don't position around benchmarks that can't be defended ("best search API," "fastest fetch").
### Access and identity language
* Docs may use `stealth`, `blocked`, `bot detection`, `anti-bot`, and similar customer language only to name or explain the problem. Never use these terms to name Browserbase, the platform, or a Browserbase product.
* Ladder from the customer's words to Browserbase's answer: their problem (`stealth`, `blocked`, or `CAPTCHA`) to **Verified**, then to the differentiation of **Agent Identity** and Web Bot Auth.
* The only valid positioning frame for `stealth` is educational: identity is stronger than stealth. Never claim that Browserbase has better stealth.
* `CAPTCHA` and `CAPTCHA solving` are allowed when describing a solution, such as "solving CAPTCHAs."
* Never use bot-protection vendor or product trademarks in public copy, including `reCAPTCHA`, `hCaptcha`, `Turnstile`, and `Cloudflare`.
* Preserve exact API parameters, error messages, code identifiers, and quoted third-party text when technical accuracy requires them. Do not promote those identifiers into headings or product names.
### Restricted use cases
Never create or approve use-case pages, demos, examples, growth hooks, SEO or SEM copy, or sales promises around:
* Apple domains, including iTunes and the App Store
* Entertainment, including Netflix and PlayStation
* Banking and other financial institutions
* Google domains without explicit approval
* Streaming
* Event ticketing, including Ticketmaster (travel such as flights is allowed)
* LinkedIn
* Mailing
Flag any copy that touches a restricted use case for PMM review before it ships.
## Product descriptions
Use these canonical descriptions when referring to Browserbase products:
* **Browserbase**: the complete platform to run agents that use the web like humans.
* **Browsers**: the core; browsers that work where APIs can't.
* **Agents**: API for reusable browser agents, with no code or deployment.
* **Search and Fetch**: primitives that give agents fast, token-efficient web context.
* **Functions**: run code next to the browser and deploy instantly, with under 5ms latency to the browser.
* **Model Gateway**: access to major models via a single Browserbase API key with unified billing.
* **Agent Identity**: the attestation layer for Web Bot Auth, Signed Agents, and agent-owned accounts.
* **Verified**: the customer-facing feature that gives agents reliable access when websites block automation.
* **Stagehand**: the SDK and AI browser driver for browser agents.
* **Browse.sh**: the open web skills catalog, accessed through the Browse CLI.
# Billing
Source: https://docs.browserbase.com/account/billing/plan-management
Manage your subscription, invoices, and payment details
## Upgrade your plan
To upgrade to Developer or Startup plans, go to [Settings](https://www.browserbase.com/settings), then **Usage & billing > Change plan**.
Upgrading to a Scale plan requires the Browserbase team. [Book a demo](https://www.browserbase.com/contact) to get started. You can also reach us at [sales@browserbase.com](mailto:sales@browserbase.com).
[Verified](/platform/identity/agent-identity) is available on Scale plans. Learn what Verified includes and how it works.
## Invoices
### Understanding your invoice
Here's the breakdown of your monthly bill:
* Base Plan Cost
* Proxy Costs
* Browser Time Costs
A Developer Plan with overages appears like this on an invoice:
You can also manage this on your Organization settings page under "Manage Billing": [https://www.browserbase.com/orgs/\{slug}/settings/billing](https://www.browserbase.com/orgs/\{slug}/settings/billing)
### Adding VAT to invoices
To add your tax ID to future invoices:
1. Go to **Settings** > **Usage** > **Manage organization plan**
2. Click **Manage billing** to open the Stripe customer portal
3. Click **Update information** and add your tax ID
Your tax ID will appear on all future invoices.
Need your tax ID added to a previous invoice? Reach out via the [support page](https://portal.usepylon.com/browserbase/forms/talk-to-an-engineer) or [support email](mailto:support@browserbase.com).
## Cancel your plan
To cancel your Browserbase plan, go to [Settings](https://www.browserbase.com/settings), then **Usage & billing > Change plan**.
# Plans
Source: https://docs.browserbase.com/account/billing/plans
Your guide to Browserbase plans, pricing, and usage limits
## Pick a plan
Browserbase scales from solo builders to enterprise teams.
Start free, no credit card needed. Includes 3 concurrent sessions.
Small projects and hobby usage: unlocks proxies.
Production workloads: 100 concurrent and priority support.
Enterprise volume, [Verified](/platform/identity/overview), and compliance
(SSO, HIPAA, DPA).
## What's included
Each plan includes a monthly allocation of browser hours and proxy bandwidth (and more). Go beyond it and pay-as-you-go [overage rates](#browser-allocations) apply. No caps. No cut-offs.
Hosted [Functions](/platform/runtime/overview) are free on every plan.
Need help estimating your plan? [Reach out here
↗](https://www.browserbase.com/contact).
### Browser allocations
Browser hours and proxy bandwidth are allocations, not limits. If you exceed them, pay-as-you-go overage rates apply.
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| ----------------- | -------- | -------------- | ------------ | ----------- |
| **Browser Hours** | 1 hr | 100 hrs | 500 hrs | Flexible |
| *Hours overage* | N/A | \$0.12/hr | \$0.10/hr | Custom |
| **Proxy** | 0 GB | 1 GB | 5 GB | Usage-based |
| *Proxy overage* | N/A | \$12/GB | \$10/GB | Custom |
Browser time is billed by the minute and proxy bandwidth by the MB, rounded,
with a one-minute and one-MB minimum per session.
### Browser capacity limits
How many browsers you can start, run at once, and for how long.
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| -------------------- | -------- | -------------- | ------------ | ---------- |
| **Concurrency** | 3 | 25 | 100 | 250+ |
| **Session Duration** | 15 mins | 6 hrs | 6 hrs | 6+ hrs |
| **Session Creation** | 5/min | 25/min | 50/min | 150+/min |
Each plan limits how many sessions you can create per minute. If you attempt to create too many sessions in a short time, you might temporarily hit your cap, even if you haven't reached max concurrency.
> For example: On the Startup Plan, your session creation limit is 50 per minute and your max concurrency is 100. It will take about 2 minutes to spin up all 100 sessions.
In most workflows, this won't be an issue. If your sessions are short-lived, consider using the [keep-alive feature](/platform/browser/long-sessions/overview#keep-alive-sessions) to reuse sessions instead of creating new ones.
### API allocations
Agents, Search, Fetch, and Extract each have a monthly allocation, and then pay-as-you-go overage rates.
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| --------------------- | ----------- | -------------- | -------------- | ---------- |
| **Agents** | 3 calls | 15 calls | 50 calls | Custom |
| **Search** | 1,000 calls | 1,000 calls | 1,000 calls | Custom |
| **Search rate limit** | 2/sec | 2/sec | 2/sec | Custom |
| *Search overage* | N/A | \$7/1k calls | \$7/1k calls | Custom |
| **Fetch** | 1,000 calls | 1,000 calls | 10,000 calls | Custom |
| **Fetch rate limit** | 5/sec | 5/sec | 5/sec | Custom |
| *Fetch overage* | N/A | \$1/1k calls | \$0.5/1k calls | Custom |
| *Extract overage* | N/A | \$4/1k calls | \$4/1k calls | Custom |
Routing through proxies costs more. Fetch with proxies is \$4/1k calls, and
Extract with proxies is \$7/1k calls.
### Identity & bot detection
How well your sessions avoid bot detection and solve captchas, from Basic to Verified.
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| ------------------------------------------- | -------- | -------------- | ------------ | ---------- |
| [**Verified**](/platform/identity/overview) | No | Basic | Basic | Verified |
| **Captcha** | No | Auto | Auto | Auto |
### Account
Settings that live on your Browserbase Organization, such as data retention, projects, and support tier.
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| ------------------ | -------- | -------------- | ------------ | ------------- |
| **Data Retention** | 7 days | 7 days | 30 days | 30+ days |
| **Projects** | 1 | Up to 2 | Up to 5 | 5+ |
| **Support** | Email | Standard | Priority | High Priority |
> Tip: Monitor your usage from [your dashboard ↗](https://www.browserbase.com/overview) to avoid surprises.
### Security & compliance
| | Free \$0 | Developer \$20 | Startup \$99 | Scale plan |
| --------- | -------- | -------------- | ------------ | ------------------- |
| **SOC2** | Yes | Yes | Yes | Pen Tests & Reports |
| **HIPAA** | No | No | No | BAA Available |
| **DPA** | No | No | No | Available |
| **SSO** | No | No | No | Available |
## Custom plans
For high-scale workloads or specific compliance needs, [book a demo ↗](https://www.browserbase.com/contact) with the Browserbase team. The team will help tailor a plan that fits your technical and business requirements. You can also reach us at [sales@browserbase.com](mailto:sales@browserbase.com).
# Bring your own storage (BYOS)
Source: https://docs.browserbase.com/account/enterprise/byos-setup-guide
Configure your own AWS S3 buckets for storing Browserbase artifacts
Bring Your Own Storage (BYOS) allows enterprise customers to store Browserbase artifacts in their own AWS S3 buckets, providing complete control over data residency and compliance.
BYOS is available only on Enterprise plans. [Contact the Browserbase team](https://www.browserbase.com/contact) to enable BYOS for your organization.
Artifacts without configured external storage automatically use Browserbase's internal storage.
***
## What gets stored
Browserbase generates several types of artifacts during sessions:
| Artifact Type | Description | Permissions needed | Status |
| ------------------------ | ---------------------------------------- | ------------------ | ----------- |
| **downloads** | Files downloaded during sessions | Write only | Available |
| **extensions** | Browser extensions loaded into sessions | Read only | Available |
| **uploads** | Files uploaded to sessions | Read/Write | Available |
| **contexts** | Session context files (cookies, storage) | Read/Write | Available |
| **recording-renditions** | Session recording MP4 downloads | Write only | Available |
| **logs** | Console logs and CDP debug output | Write only | Coming Soon |
You can configure some or all artifact types for external storage. Unconfigured types will use Browserbase's internal buckets.
**Session logs** are currently stored in Browserbase's managed infrastructure; BYOS support for logs is planned for a future release. Browserbase enables recording downloads (`recording-renditions`) per project. Contact your account manager to turn them on.
***
## Setup instructions
### Step 1: Create S3 buckets
Create S3 buckets in your AWS account for the artifact types you want to store externally.
```bash theme={null}
# Create buckets in your desired regions
aws s3 mb s3://company-browserbase-downloads --region us-east-1
aws s3 mb s3://company-browserbase-contexts --region us-west-2
# A single bucket can store multiple artifact types
# They'll be separated by path prefix (downloads/, contexts/, extensions/, uploads/)
aws s3 mb s3://company-browserbase-storage --region us-east-1
```
### Step 2: Generate external ID
Generate a cryptographically random external ID for secure role assumption:
```bash theme={null}
openssl rand -hex 32
```
Store the External ID securely. You'll need it for both the IAM role configuration and when providing credentials to Browserbase.
### Step 3: Create IAM role
Create an IAM role that Browserbase will assume to access your buckets.
**Trust Policy** (`trust-policy.json`):
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::BROWSERBASE_ACCOUNT_ID:role/svc_byos_accessor"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "YOUR_EXTERNAL_ID_FROM_STEP_2"
}
}
}
]
}
```
This follows the [AWS third-party access pattern](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_common-scenarios_third-party.html). The principal is Browserbase's dedicated BYOS accessor role, which is used by all internal services that need to access your buckets.
Contact Browserbase via your account manager or [support@browserbase.com](mailto:support@browserbase.com) for Browserbase's AWS account ID.
The role name `svc_byos_accessor` is fixed and should not be changed. This is Browserbase's internal service role that assumes your customer role.
```bash Create Role theme={null}
aws iam create-role \
--role-name browserbase-byos-access \
--assume-role-policy-document file://trust-policy.json
```
**Role name must start with `browserbase-`**. For security, Browserbase can only assume roles matching the pattern `browserbase-*`. Examples: `browserbase-byos-access`, `browserbase-storage`, `browserbase-prod`.
### Step 4: Configure S3 permissions
Attach permissions to your IAM role for bucket access.
**Permissions Policy** (`permissions-policy.json`):
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BrowserbaseReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-browserbase-extensions",
"arn:aws:s3:::company-browserbase-extensions/*"
]
},
{
"Sid": "BrowserbaseReadWriteUploads",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-browserbase-uploads",
"arn:aws:s3:::company-browserbase-uploads/*"
]
},
{
"Sid": "BrowserbaseWriteAccess",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-browserbase-downloads",
"arn:aws:s3:::company-browserbase-downloads/*"
]
},
{
"Sid": "BrowserbaseReadWriteAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-browserbase-contexts",
"arn:aws:s3:::company-browserbase-contexts/*"
]
}
]
}
```
**Why ListBucket?** This permission on the bucket itself (without `/*`) is required for:
* Paginating through large numbers of objects efficiently
* Checking if objects exist before operations
* Proper error handling when artifacts are missing
Without it, Browserbase can't determine if a missing artifact is due to permissions or if it truly doesn't exist.
```bash Attach Policy theme={null}
aws iam put-role-policy \
--role-name browserbase-byos-access \
--policy-name browserbase-s3-access \
--policy-document file://permissions-policy.json
```
### Step 5: Submit configuration
Send this information to your Browserbase account manager or [support@browserbase.com](mailto:support@browserbase.com):
```json theme={null}
{
"role_arn": "arn:aws:iam::123456789012:role/browserbase-byos-access",
"external_id": "a1b2c3d4e5f6...",
"project_ids": ["proj_abc123", "proj_def456"],
"buckets": [
{
"artifact_type": "downloads",
"bucket_arn": "arn:aws:s3:::company-browserbase-downloads",
"region": "us-east-1"
},
{
"artifact_type": "contexts",
"bucket_arn": "arn:aws:s3:::company-browserbase-contexts",
"region": "us-west-2"
},
{
"artifact_type": "uploads",
"bucket_arn": "arn:aws:s3:::company-browserbase-uploads",
"region": "us-east-1"
},
{
"artifact_type": "extensions",
"bucket_arn": "arn:aws:s3:::company-browserbase-extensions",
"region": "us-east-1"
}
]
}
```
**Required fields:**
* `role_arn`: The IAM role ARN from Step 3
* `external_id`: The external ID from Step 2
* `project_ids`: Array of project IDs that should use this BYOS configuration
* `buckets`: Array of bucket configurations with artifact\_type, bucket\_arn, and region
***
## Configuration examples
### Minimal setup (downloads only)
```json theme={null}
{
"role_arn": "arn:aws:iam::123456789012:role/browserbase-byos-access",
"external_id": "a1b2c3d4...",
"project_ids": ["proj_abc123"],
"buckets": [
{
"artifact_type": "downloads",
"bucket_arn": "arn:aws:s3:::company-downloads",
"region": "us-east-1"
}
]
}
```
### Complete setup (all available artifact types)
```json theme={null}
{
"role_arn": "arn:aws:iam::123456789012:role/browserbase-byos-access",
"external_id": "a1b2c3d4...",
"project_ids": ["proj_abc123", "proj_def456"],
"buckets": [
{
"artifact_type": "downloads",
"bucket_arn": "arn:aws:s3:::company-downloads",
"region": "us-east-1"
},
{
"artifact_type": "contexts",
"bucket_arn": "arn:aws:s3:::company-contexts",
"region": "us-east-1"
},
{
"artifact_type": "extensions",
"bucket_arn": "arn:aws:s3:::company-extensions",
"region": "us-east-1"
},
{
"artifact_type": "uploads",
"bucket_arn": "arn:aws:s3:::company-uploads",
"region": "us-east-1"
}
]
}
```
### Multi-region setup
```json theme={null}
{
"role_arn": "arn:aws:iam::123456789012:role/browserbase-byos-access",
"external_id": "a1b2c3d4...",
"project_ids": ["proj_abc123"],
"buckets": [
{
"artifact_type": "downloads",
"bucket_arn": "arn:aws:s3:::company-downloads-us",
"region": "us-east-1"
},
{
"artifact_type": "contexts",
"bucket_arn": "arn:aws:s3:::company-contexts-eu",
"region": "eu-central-1"
},
{
"artifact_type": "uploads",
"bucket_arn": "arn:aws:s3:::company-uploads-ap",
"region": "ap-southeast-1"
}
]
}
```
### Single bucket for all types
```json theme={null}
{
"role_arn": "arn:aws:iam::123456789012:role/browserbase-byos-access",
"external_id": "a1b2c3d4...",
"project_ids": ["proj_abc123", "proj_def456", "proj_ghi789"],
"buckets": [
{
"artifact_type": "downloads",
"bucket_arn": "arn:aws:s3:::company-browserbase-storage",
"region": "us-east-1"
},
{
"artifact_type": "contexts",
"bucket_arn": "arn:aws:s3:::company-browserbase-storage",
"region": "us-east-1"
},
{
"artifact_type": "extensions",
"bucket_arn": "arn:aws:s3:::company-browserbase-storage",
"region": "us-east-1"
},
{
"artifact_type": "uploads",
"bucket_arn": "arn:aws:s3:::company-browserbase-storage",
"region": "us-east-1"
}
]
}
```
***
## Troubleshooting
**Check:**
1. Trust policy principal includes Browserbase's AWS account ID
2. External ID in trust policy matches what you provided to Browserbase
3. Role has permissions for specific bucket ARNs
4. Both bucket-level and object-level permissions are granted
**Common Fix:**
Ensure your bucket policy or role policy includes both:
```
arn:aws:s3:::bucket-name (for ListBucket)
arn:aws:s3:::bucket-name/* (for GetObject/PutObject)
```
**Symptom:** `PermanentRedirect` or "bucket must be accessed via specific endpoint"
**Fix:**
* Verify the region in your configuration matches your bucket's actual region
* S3 buckets are region-specific and must be accessed via the correct endpoint
**Check:**
1. Artifact type is correctly configured
2. Bucket path structure: `{artifactType}/{sessionId}/{key}`
3. Write permissions (`s3:PutObject`) are granted
4. No bucket policies blocking access
***
## FAQs
Yes, use the `project_ids` field to specify which projects should use a BYOS configuration. You can create multiple configurations with different `project_ids` arrays to route different projects to different buckets.
Yes, Browserbase separates artifact types using path prefixes (e.g., `downloads/`, `contexts/`, `extensions/`, `uploads/`), so they won't conflict in a shared bucket.
Historical artifacts remain in Browserbase's internal storage. Only new artifacts created after BYOS is enabled will be stored in your buckets. Contact support if you need to migrate existing data.
No, configure only the types you want to store externally. Unconfigured types automatically use Browserbase's internal storage.
Yes, delete the IAM role or modify the trust policy to remove Browserbase's principal. This immediately prevents access, but active sessions may fail if they can't write artifacts.
You control retention through S3 lifecycle policies on your buckets. Browserbase doesn't delete artifacts from your external buckets. You manage the full lifecycle.
Browserbase organizes artifacts with this path structure:
```
{artifact_type}/{sessionId}/{key}
```
**Examples:**
* `downloads/session-abc123/document.pdf`
* `contexts/session-abc123/cookies.json`
* `extensions/session-abc123/extension.crx`
* `uploads/session-abc123/input-file.csv`
This structure allows you to use the same bucket for multiple artifact types, separated by path prefix.
**CloudTrail Logging:**
* Audit `AssumeRole` calls from Browserbase by AWS account ID
* Track S3 operations (`GetObject`, `PutObject`)
* Monitor failed access attempts for security
**CloudWatch Alarms:**
* Alert on failed `AssumeRole` attempts
* Monitor `4xx` error rates on your buckets
* Track storage growth
**S3 Lifecycle Policies:**
* Configure automatic archival to Glacier for cost savings
* Set expiration rules based on compliance requirements
**Encryption:**
```bash theme={null}
aws s3api put-bucket-encryption \
--bucket your-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}]
}'
```
# Enterprise security
Source: https://docs.browserbase.com/account/enterprise/security
SOC 2 Type II, HIPAA compliance, zero-trust browser isolation, and enterprise-grade security for Browserbase infrastructure.
Browserbase is secure infrastructure for browser agents at scale. Leading companies in finance, healthcare, and government trust Browserbase with sensitive data. For full details, visit the [Trust Center](https://trust.browserbase.com/).
## Security architecture at a glance
| Feature | Implementation |
| -------------------- | --------------------------------------------------------------- |
| **Isolation** | 1 browser per VM |
| **Network security** | Individual subnets and strict firewall rules |
| **Logging control** | Optional; disable logs and session video recordings at any time |
| **Data residency** | Configurable by region (US/EU/Asia) |
| **Model control** | BYO-LLM with full interceptor customization |
| **Compliance** | SOC 2 Type II, HIPAA, third-party pen testing |
***
## Why enterprises trust Browserbase
### Enterprise-grade compliance
* **SOC 2 Type II Certified**
* **HIPAA compliant**: Business Associate Agreements (BAAs) available
* Comprehensive third-party **penetration testing** with successful results
* Full access to security documentation (e.g., auditor attestations, compliance portal access) provided on request
### Secure by design
#### Zero trust browser isolation
Browserbase follows a zero-trust architecture, assuming that any browser may be compromised. This design philosophy ensures comprehensive containment and minimizes risk.
* **Browser isolation**: Each browser runs in a dedicated VM, providing strong isolation at the hardware virtualization layer.
* **Network isolation**: Every browser runs in an isolated subnet with strict firewalls to prevent lateral movement.
* **No browser reuse**: After each session, the virtual machine is killed and recreated from scratch, ensuring every browser session is completely fresh.
* **No GPU access**: To avoid known security risks associated with shared GPU memory attacks.
#### Patch management
* Continuous updates with fast turnaround for critical CVEs
* Browserbase patches Chrome versions proactively and automatically.
* Compatibility testing for enterprise environments ensures stability across browser updates
### Transparent data handling
* **Zero data retention**: [Disable logging](/reference/api/create-a-session#body-browser-settings-log-session) and [session recording](/reference/api/create-a-session#body-browser-settings-record-session) via the [Create Session API](/reference/api/create-a-session) to ensure no data is recorded.
* **Configurable browser regions**: Choose your closest data center to minimize latency and ensure data sovereignty
* US West
* US East
* EU (Germany)
* Asia (Singapore)
### Guardrails for AI web browsing
Browserbase offers AI-assisted browsing via Stagehand. It's designed for safe, deterministic agent workflows.
#### Stagehand benefits
* **Atomic, auditable steps**: Workflows are built as a series of deterministic, cacheable commands
* **Self-healing actions**: If a selector changes, Stagehand retries using an LLM fallback only for that atomic step
* **No full-page hallucinations**: Minimizes AI unpredictability by limiting LLM scope
* **Prompt templating**: Sensitive data can be parameterized and injected at runtime to avoid LLM exposure
* **Bring your own model (BYO-LLM)**: Maintain full control with your preferred model, keys, and compliance framework
***
## Summary
Browserbase is designed for scalable browser agents, architected with security and trust at its core. With enterprise-grade controls, strict isolation, and transparent compliance, organizations can confidently scale sensitive workloads without compromising security.
For regulated industries, sensitive workflows, or AI-powered browser agents under strict oversight, Browserbase is the secure infrastructure you can depend on.
**Questions?** Contact the security team or request full compliance documentation through the [Trust Center](https://trust.browserbase.com/).
# Zero data retention (ZDR)
Source: https://docs.browserbase.com/account/enterprise/zero-data-retention
Run Browserbase sessions for regulated workloads without persisting logs, recordings, or replay data.
Zero Data Retention (ZDR) lets your account run Browserbase sessions without persisting **session logs**, **session recordings**, or **replay artifacts** to Browserbase's managed storage. Combined with [Bring Your Own Storage (BYOS)](/account/enterprise/byos-setup-guide), ZDR gives Enterprise customers full control over where every artifact produced by a session lives, including the option for no artifact to leave your environment at all.
ZDR is available only on Enterprise plans. [Contact the Browserbase team](https://www.browserbase.com/contact) to learn more about Zero Data Retention for your organization.
***
## What ZDR covers
When ZDR is enabled, Browserbase **does not persist** the following artifacts:
| Artifact | Behavior under ZDR |
| ------------------- | --------------------------------------------------------------------------------- |
| **Session logs** | CDP and console output are not written to Browserbase storage |
| **Video recording** | No `.mp4` is encoded or stored |
| **Replay (DOM)** | No RRweb replay is captured; the Replay API returns `404 Not Found` |
| **Live View** | Still available in real time. Live View streams the session, it does not store it |
The browser session itself runs normally. Your automation, downloads, and uploads work the same way. ZDR only changes whether Browserbase keeps a record of what happened inside the session after it ends.
**Downloads, uploads, contexts, and extensions** are separate artifact types. If you need those stored under your control as well, pair ZDR with [BYOS](/account/enterprise/byos-setup-guide) so they're written to your own S3 buckets.
***
## How to enable ZDR
You can opt individual sessions out of logging and recording by setting `logSession` and `recordSession` to `false` when you [create a session](/reference/api/create-a-session). This works on every plan and is useful for sensitive workflows where you don't want Browserbase to persist logs or recordings.
If you'd also like artifacts (downloads, contexts, uploads, and extensions) stored exclusively in your AWS account, pair ZDR with [BYOS](/account/enterprise/byos-setup-guide). The two features work together.
```typescript theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create({
projectId: process.env.BROWSERBASE_PROJECT_ID!,
browserSettings: {
logSession: false,
recordSession: false,
},
});
```
```python theme={null}
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
project_id=os.environ["BROWSERBASE_PROJECT_ID"],
browser_settings={
"log_session": False,
"record_session": False,
},
)
```
```bash theme={null}
curl -X POST https://api.browserbase.com/v1/sessions \
-H "X-BB-API-Key: $BROWSERBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "'"$BROWSERBASE_PROJECT_ID"'",
"browserSettings": {
"logSession": false,
"recordSession": false
}
}'
```
Both flags default to `true`. Setting either to `false` is permanent for that session. You cannot re-enable logging or recording after the session has started.
***
## What changes for your team
* **Live View** continues to work for real-time debugging of in-flight sessions.
* **Session Replay** and **Recording** endpoints return `404 Not Found` for ZDR sessions because there is no recording to fetch.
* **Session Logs API** returns no entries for ZDR sessions.
* **Dashboard** still shows session metadata (status, start/end time, region, and duration), but the replay viewer will be empty.
* **Support investigations** are limited under ZDR. If you open a support ticket, Browserbase will not have logs or replay to reference. Browserbase recommends keeping logs and recording enabled for non-production projects so issues can be reproduced and triaged.
Once a ZDR session ends, the logs and replay data are gone. Browserbase does not retain them, even for support. Make sure your own observability (application logs, Stagehand `history` and `metrics`, and custom telemetry) captures everything you need before disabling these features.
***
## Pairing ZDR with BYOS
ZDR removes Browserbase-managed logs and recordings. BYOS routes the remaining session artifacts (downloads, uploads, contexts, and extensions) into S3 buckets you own.
Together they give you a configuration where:
* No session logs or recordings exist on Browserbase storage.
* Browserbase writes all other session artifacts directly to your AWS account, under your KMS keys, with your lifecycle policies.
* Browserbase retains only operational metadata (session ID, project, timing, and region) needed to run the platform and bill usage.
See the [BYOS setup guide](/account/enterprise/byos-setup-guide) for how to provision the buckets and IAM role.
***
## Compliance and contracts
ZDR is part of Browserbase's Enterprise security posture. See [Enterprise security](/account/enterprise/security) for the full picture, including SOC 2 Type II, HIPAA BAA availability, and the [Trust Center](https://trust.browserbase.com/).
If your contract or compliance program requires written confirmation that ZDR is enabled for your account, ask your Browserbase contact. Browserbase can provide attestation as part of your enterprise agreement.
***
## FAQ
No. Disabling logs and recording slightly reduces CPU and network overhead inside the session VM, but the difference is not user-visible.
Yes. Set `logSession: false` / `recordSession: false` per-session for the workloads that need it. You can apply this selectively in your code based on project, environment, or workflow.
No. Live View streams the session in real time without storing it, so it remains fully functional. ZDR only suppresses the persisted replay/recording.
Yes. Stagehand creates Browserbase sessions under the hood, so the same `logSession` and `recordSession` settings apply via `browserbaseSessionCreateParams`.
ZDR controls what Browserbase stores. Those providers govern any data you send to third-party LLMs (OpenAI, Anthropic, etc.). Use [BYO-LLM](/account/enterprise/security#guardrails-for-ai-web-browsing) to route AI calls through your own keys and provider agreements.
***
**Questions?** Contact the Browserbase team to learn more about ZDR, or request full compliance documentation through the [Trust Center](https://trust.browserbase.com/).
# Teams
Source: https://docs.browserbase.com/account/team/roles
Manage team members, roles, and project access
## Add teammates
Select the Organization you want to add a team member to.
Select "Settings" from the menu.
Click "Invite members" from the Pending Invitations section.
Type in the email address of the team member you want to add and send the invitation.
## User roles
There are three roles a user can have within an organization: Admin, Contributor, and Viewer.
| | Admin | Contributor | Viewer |
| ----------------------------- | ----- | ----------- | ------ |
| Invite new users | ✅ | ❌ | ❌ |
| Manage users | ✅ | ❌ | ❌ |
| Create projects | ✅ | ❌ | ❌ |
| View sessions | ✅ | ✅ | ✅ |
| Stop sessions | ✅ | ✅ | ❌ |
| View plan usage | ✅ | ✅ | ❌ |
| Run scripts in the playground | ✅ | ✅ | ❌ |
| Change project settings | ✅ | ✅ | ❌ |
| View API key | ✅ | ✅ | ❌ |
| Regenerate API key | ✅ | ❌ | ❌ |
User roles **only** affect what permissions a user has within the dashboard itself. They don't change who can interact with sessions via the API. That's what your API key is used for.
You can manage and change what roles members have in your organization from [Settings > Members](https://www.browserbase.com/settings). Navigate to your organization using the switcher in the main menu, then select Members.
When inviting new users, you'll also assign a role to them at the same time.
## Project access
When you invite a new user to your organization, you can select which projects the user should have access to in addition to their role:
Users that have "all projects" selected will be able to access all current projects in addition to any projects created in the future. If you need to restrict a user to only existing projects, select them individually in the drop down.
Admins can't have their project access restricted. Users need either the contributor or viewer role to restrict their project access.
## Single sign-on (SSO)
For enterprise teams, Browserbase supports SAML 2.0-based Single Sign-On so your team can log in with your corporate identity provider.
Learn how to configure SSO for your organization.
# SSO (single sign-on) with SAML
Source: https://docs.browserbase.com/account/team/sso
Learn how to enable SAML 2.0-based Single Sign-On for your Browserbase organization
Browserbase supports **SAML 2.0-based Single Sign-On (SSO)** so your team can log in with your corporate identity provider (IdP).
SSO is available only on Enterprise plans. [Get in touch](https://www.browserbase.com/contact) with the Browserbase team to enable SSO on your account.
## Supported identity providers
Any **SAML 2.0-compliant IdP** is supported, including:
* Okta Workforce
* Microsoft Entra ID (formerly Azure AD)
* Google Workspace (SAML)
* Custom SAML providers
## How setup works
Setting up SSO requires coordination between your IT team and Browserbase support. Here's the process:
**Example:** Acme Corp wants to enable Okta SSO for their Browserbase organization.
### Step 1: Your IT team shares IdP configuration
Your IT administrator sends the following details from your identity provider to [support@browserbase.com](mailto:support@browserbase.com):
* **Sign-on URL (SSO URL)** - Where Browserbase redirects users for authentication
* **Entity ID / Issuer** - Your IdP's unique identifier
* **X.509 Signing Certificate** - Used to verify SAML assertions
### Step 2: Browserbase provides service provider details
The Browserbase team responds with configuration values your IT team needs:
* **Assertion Consumer Service (ACS) URL** - Where your IdP sends authentication responses
* **Entity ID (Audience URI)** - Browserbase's unique identifier
* **Metadata URL** - Complete SAML configuration (preferred method)
### Step 3: Your IT team configures the SAML application
Your administrator creates a new SAML application in your IdP (e.g., Okta, Azure AD) using the Browserbase SP details.
### Step 4: Joint testing
Both teams coordinate to test the login flow and verify that user attributes are mapped correctly.
### Step 5: Browserbase enables SSO
Once testing is successful, the Browserbase team enables SSO for your organization.
## Required attributes
Browserbase requires the following attributes in the SAML assertion:
* **Email address** – The user's email
* **First name** – The user's given name
* **Last name** – The user's surname
### Okta configuration
In your Okta SAML application, navigate to **SAML Settings** and configure the **Attribute Statements** section:
| Name | Name Format | Value |
| ----------- | ----------- | ---------------- |
| `mail` | Unspecified | `user.email` |
| `firstName` | Unspecified | `user.firstName` |
| `lastName` | Unspecified | `user.lastName` |
**Name** is the attribute name Browserbase expects. These must match exactly as shown (`mail`, `firstName`, `lastName`).
**Value** is the Okta expression that retrieves the data from your directory. The values shown above (`user.email`, `user.firstName`, `user.lastName`) are Okta's default user profile attributes. If your organization uses custom attributes, adjust the Value accordingly (e.g., `user.primaryEmail` or `appuser.email`).
### Other identity providers
For other SAML providers (Microsoft Entra ID, Google Workspace, etc.), ensure your attribute statements use the same attribute names:
* `mail` for email address
* `firstName` for given name
* `lastName` for surname
The source values will differ based on your IdP's attribute schema.
## Testing
**SP-initiated login** (recommended): Start from the Browserbase login page → redirected to your IdP → redirected back after successful authentication.
## Just-in-Time (JIT) provisioning
Browserbase supports **JIT provisioning** for all SAML providers. The first time a user authenticates through your IdP, a Browserbase account is automatically created for them, with no manual invitation required. User attributes (name, email) are also kept in sync on every sign-in, so updates in your directory propagate automatically.
JIT is enabled by default on all SSO connections. Make sure JIT is also enabled on your IdP side (see your IdP's documentation, e.g. [Okta's JIT guide](https://help.okta.com/en-us/content/topics/security/jit-overview.htm)).
## Next steps
1. Collect your IdP configuration values.
2. Share them with Browserbase at [support@browserbase.com](mailto:support@browserbase.com).
3. The Browserbase team will reply with the SP details.
4. Test and finalize the integration together.
Once complete, your users can securely log in to Browserbase with SSO.
# Set up 1Password Nightly Autofill for Director
Source: https://docs.browserbase.com/integrations/1password/director
Enable agentic autofill in 1Password to securely authenticate with Director AI.
This setup enables **Agentic Autofill**, a Nightly feature that allows AI agents like Director to securely request and use credentials from your 1Password vault.
## Prerequisites
Before you begin, make sure you have:
* [A Browserbase account](https://www.browserbase.com/sign-up)
* [A 1Password Business account](https://1password.com/business-pricing)
A 1Password Business account is required for Policies access (\$7.99/mo). On a 1Password Enterprise or Extended Access Management plan? [Contact Browserbase](https://portal.usepylon.com/browserbase/forms/talk-to-an-engineer) if you run into any issues.
This guide uses **1Password Nightly**, a pre-release channel with experimental features. It's recommended for testing and development only.
***
## Admin setup (one-time)
You need **admin access** to a **1Password Business account** to enable Agentic Autofill policies.
This is a one-time setup per 1Password account/workspace. Once enabled, all members can use Agentic Autofill.
### Enable Agentic Autofill
Sign in to [1Password.com](https://1password.com) and go to **Policies** in the left sidebar:
Click **Sharing and permissions** then **Manage policies**:
Scroll to the bottom and enable **Agentic permissions** to turn on Agentic Autofill:
**Important:** Scroll to the very bottom and click **Save**.
***
## User setup
### 1. Install 1Password desktop app
Download and install the latest 1Password desktop application.
Visit the [1Password download page](https://1password.com/downloads/) and download the latest version.
If you have 1Password installed via Kandji or another management system:
1. Open **Finder**
2. Navigate to **Applications**
3. Right-click **1Password** and choose **Move to Trash**
If 1Password is still running in the macOS menu bar after closing, right-click the menu bar icon and select **Quit**.
Then install the new version:
1. In your **Downloads** folder, locate **1Password.zip**
2. Double-click to unzip and reveal "1Password Installer"
3. Open the installer and follow the prompts
***
### 2. Switch to the Nightly channel
When 1Password opens, your existing Browserbase account should appear automatically.
In the upper-left of the app, click your **account name** and then click **Settings** at the bottom of the menu.
Find the **Release channel** dropdown and change it to **Nightly**:
Click **Check for updates** in the About section:
Download and restart when prompted:
If the app still shows the stable channel after restarting, re-open Settings and confirm **Nightly** is selected, then check for updates again.
***
### 3. Add login credentials for testing
Add test credentials for saucedemo.com as an example.
Click the **+ New Item** button in the upper-right and choose **Login**:
Enter the following details:
* **Username**: `standard_user`
* **Password**: `secret_sauce`
* **Website**: `saucedemo.com`
Click **Save**:
You can add any website credentials you want to use with [Director](https://director.ai). This example uses saucedemo.com for testing.
***
### 4. Install 1Password Nightly browser extension
Visit the [Chrome Web Store](https://chromewebstore.google.com/detail/1password-nightly-–-passw/gejiddohjgogedgjnonbofjigllpkmbf?hl=en) and click **Add to Chrome** to install the **1Password Nightly** extension.
Follow the prompts to complete the installation.
***
### 5. Connect Director with 1Password
Go to [Director.ai](https://director.ai) and sign in with your **Browserbase account** in the upper-right:
Click "Connect with 1Password":
Follow the prompt to **Login with 1Password**. You should return to director.ai with the integration enabled:
***
### 6. Try it out
In the Director prompt, type:
```
Navigate to saucedemo.com and login with 1Password
```
Wait for Director to navigate to the site (\~10 seconds). Director will click **Login with 1Password**:
You should see a credential selection prompt:
Select your **saucedemo.com** credentials from the prompt. Director will automatically fill in the username and password, and the login should complete successfully:
Congratulations! You've successfully set up 1Password Nightly Autofill with Director.
***
## Troubleshooting
* Verify you installed the **1Password Nightly** extension (not the stable version)
* Ensure the extension is enabled in Chrome at `chrome://extensions`
* Confirm you're signed in to Director with your Browserbase account
* Refresh the Director page and try enabling the integration again
* Verify the login item exists in your 1Password vault
* Ensure the website field matches the site you're trying to access
* Check that Agentic permissions are enabled in your account policies
* Re-open 1Password Settings
* Confirm **Nightly** is selected in the Release channel dropdown
* Manually check for updates again
***
## Next steps
Learn how to programmatically access 1Password credentials using the SDK
Use contexts to maintain authenticated sessions across workflows
# Introduction
Source: https://docs.browserbase.com/integrations/1password/introduction
Securely log into websites in a Browserbase session using 1Password.
By the end of this guide, your **Browserbase** agent will use **1Password** to log into a website, without hardcoding credentials into your code.
You'll learn how to:
1. Set up a 1Password vault for your credentials
2. Create a 1Password Service Account for programmatic access
3. Use the 1Password SDK to retrieve credentials from your vault
4. Automate website logins in your Browserbase session
A **new 1Password integration experience** is coming: simpler setup, faster logins, and native support.
Stay tuned or reach out to get early access.
## When you'd use this
Use the 1Password integration when you need to:
* Automate logins without storing raw credentials in code or environment files.
* Give your agent access to credentials in a secure, auditable way.
* Rotate passwords in your vault without updating scripts.
* Share access across team members without exposing sensitive data.
This is ideal for:
* Testing logins on staging environments
* Monitoring tools that need to log into internal dashboards
* AI agents that manage or navigate multiple accounts
## Prerequisites
Before you start, make sure you have:
* [A Browserbase account](https://www.browserbase.com/sign-up)
* [A 1Password account](https://start.1password.com/)
> The quickstart guide will walk you through creating a Service Account and setting up your vault.
## Quickstart guides
Follow the step-by-step quickstart guide to use your 1Password Service Account to automate your first login.
# Use 1Password to log in with Browserbase
Source: https://docs.browserbase.com/integrations/1password/quickstart
Securely retrieve credentials from your 1Password vault and use them to log into websites.
This guide walks you through using the 1Password SDK with Stagehand and Playwright to securely authenticate on websites without hardcoding credentials.
This guide assumes you have a 1Password account and a Browserbase account. If you don't, create a [1Password account](https://start.1password.com/) and [Browserbase account](https://www.browserbase.com/sign-up).
## 1. Create your 1Password vault
Create a specific vault structure for this integration.
### Create the vault
1. Open the 1Password app and sign in to your account
2. Click the plus **+** button in the sidebar
3. Name your vault: **`Browserbase Agent`**
4. Optionally add a description like "Credentials for Browserbase automation"
5. Click **Create**
1. Sign in to your account on [1Password.com](https://1password.com)
2. Click **New Vault** on your Home page
3. Name your vault: **`Browserbase Agent`**
4. Optionally add a description like "Credentials for Browserbase automation"
5. Click **Create Vault**
### Add your Browserbase credentials
1. In your new **`Browserbase Agent`** vault, click **New Item**
2. Select **Login** as the item type
3. Fill in the details:
* **Title**: `Browserbase`
* **Username**: Your Browserbase email address
* **Password**: Your Browserbase password
4. Click **Save**
Your vault structure should now be: `op://Browserbase Agent/Browserbase/username` and `op://Browserbase Agent/Browserbase/password`
## 2. Create a service account
Create a Service Account to allow programmatic access to your vault.
You need admin access to your 1Password account to create a Service Account.
1. Navigate to [1Password.com](https://1password.com) and sign in
2. Go to the **Developer** tab in the sidebar
3. Under **Service Accounts**, click **New Service Account**
4. Name your Service Account: **`Browserbase Agent Account`**
5. Select the vault you created in Step 1: **`Browserbase Agent`**
6. Click **Create Account**
7. **IMPORTANT:** Copy the Service Account token that appears
This Service Account token is sensitive and will only be shown once. Store it securely - you'll need it for the `OP_SERVICE_ACCOUNT_TOKEN` environment variable.
## 3. Install dependencies
```bash theme={null}
npm install @browserbasehq/stagehand @1password/sdk
```
```bash theme={null}
pnpm add @browserbasehq/stagehand @1password/sdk
```
```bash theme={null}
yarn add @browserbasehq/stagehand @1password/sdk
```
## 4. Configure environment
Create a `.env` file in your project root:
```env .env theme={null}
BROWSERBASE_API_KEY=your_browserbase_api_key
OPENAI_API_KEY=your_openai_api_key
OP_SERVICE_ACCOUNT_TOKEN=your_1password_service_account_token
```
## 5. Implement authentication
Create an `index.ts` file with the following code:
```typescript index.ts theme={null}
import { z } from "zod";
import { createClient } from "@1password/sdk";
import { Stagehand } from "@browserbasehq/stagehand";
async function main() {
// Initialize 1Password SDK client for secure credential retrieval
const client = await createClient({
auth: process.env.OP_SERVICE_ACCOUNT_TOKEN!,
integrationName: "My Browserbase and 1Password Integration",
integrationVersion: "v1.0.0",
});
// Initialize Stagehand with Browserbase environment
const stagehand = new Stagehand({
env: "BROWSERBASE",
});
await stagehand.init();
// Retrieve credentials from 1Password vault
const username = await client.secrets.resolve("op://Browserbase Agent/Browserbase/username");
const password = await client.secrets.resolve("op://Browserbase Agent/Browserbase/password");
// Navigate to Browserbase sign-in page
const page = stagehand.context.pages()[0];
await page.goto("https://www.browserbase.com/sign-in", { waitUntil: "domcontentloaded" });
console.log('Navigated to Browserbase sign-in page')
// Login process
await stagehand.act(`Type in the username: ${username}`);
console.log('Typed in the username')
await stagehand.act('Click continue')
console.log('Clicked continue')
await stagehand.act(`Type in the password: ${password}`);
console.log('Typed in the password')
await stagehand.act('Click the sign in button')
console.log('Clicked the sign in button')
// Wait for the page to load
await page.waitForLoadState('domcontentloaded')
console.log('Page loaded')
// Extract project information from the dashboard
const { projectId } = await stagehand.extract(
"Extract the project ID of the Browserbase account",
z.object({
projectId: z.string(),
}),
);
const { totalSessions } = await stagehand.extract(
"Extract the total sessions for the period for the project",
z.object({
totalSessions: z.number(),
}),
);
console.log('For Project ID: ', projectId, ' the total sessions for the last 30 days is: ', totalSessions)
// Close the stagehand session
await stagehand.close();
}
main();
```
## 6. Run your script
Execute your script to see the 1Password integration in action:
```bash theme={null}
npx tsx index.ts
```
```bash theme={null}
pnpm tsx index.ts
```
```bash theme={null}
yarn tsx index.ts
```
## Next steps
Learn more about using contexts to automate workflows across login-gated sites
Learn more about using session recordings to debug and optimize your workflows
# AgentKit by Tools for Humanity
Source: https://docs.browserbase.com/integrations/agentkit/introduction
Use AgentKit to prove your AI agent is backed by a real human with cryptographic proof-of-humanity.
**AgentKit** is a developer SDK from [Tools for Humanity](https://world.org) that lets AI agents cryptographically prove they're operating on behalf of a verified human. It uses [World ID](https://world.org/world-id), the largest human identity network, to link agent wallets to unique, Orb-verified humans on the [World Chain](https://world.org/world-chain).
Browserbase integrates AgentKit to give human-verified agents access to [Verified browsers](/platform/identity/verified-customization), purpose-built Chromium recognized by bot protection partners, without requiring API keys, accounts, or KYC.
AgentKit verification is currently available through the [x402 payment rail](/integrations/x402/introduction). Agents pay with USDC on Base and prove humanity with AgentKit to unlock premium features.
## Why it matters
Autonomous agents operating on the open web need a way to prove legitimacy. API keys prove account ownership. AgentKit proves something stronger: **a real human is behind this agent.**
This matters for:
* **Permissionless access**: agents can get premium browsers without signing up for an account
* **Abuse prevention**: Verified browsers are reserved for human-backed agents
* **Privacy**: agents prove humanity without revealing who the human is
* **Composability**: the same wallet can pay for sessions (x402) and prove identity (AgentKit)
## How it works
The agent operator verifies their identity through [World App](https://world.org/world-app) using Orb verification (a one-time biometric check that confirms you're a unique human).
The operator registers an EVM wallet address in the [AgentBook](https://docs.world.org/agents/agent-kit) smart contract on World Chain. This links the wallet to a verified human identity.
Before each browser session request, the agent signs an [EIP-191](https://eips.ethereum.org/EIPS/eip-191) message proving it controls a registered wallet. The proof includes the target URL and a short TTL for replay protection.
The Browserbase x402 gateway verifies the signature, checks AgentBook registration on World Chain, and upgrades the session to a Verified browser.
## What agents get
| Scenario | Browser |
| --------------------------- | ------------ |
| x402 payment only | Standard |
| x402 + valid AgentKit proof | **Verified** |
The gateway never rejects a paid request over a missing or invalid proof, it silently falls back to a standard browser. Agents always get a working session.
## Getting started
Set up AgentKit verification and create your first human-verified browser session
Deep dive into the x402 payment flow with AgentKit premium unlock
## Further reading
Official AgentKit documentation from Tools for Humanity
The blockchain powering AgentBook identity verification
How Browserbase handles agent authentication across all layers
# AgentKit quickstart
Source: https://docs.browserbase.com/integrations/agentkit/quickstart
Set up AgentKit human verification and create your first verified browser session via x402.
This guide walks you through registering a wallet in AgentBook, signing proof headers, and creating a premium x402 browser session with a Verified browser.
## Prerequisites
* An EVM wallet (any wallet that can sign messages)
* [World App](https://world.org/world-app) with Orb verification completed
* USDC on Base network (for x402 session payments)
* Node.js 18+
## Setup
### 1. Install dependencies
```bash theme={null}
npm install @worldcoin/agentkit viem x402-fetch
```
### 2. Register your wallet in AgentBook
AgentBook is a smart contract on World Chain that links wallet addresses to verified humans. Registration is a one-time on-chain transaction.
```bash theme={null}
npx @worldcoin/agentkit-cli register
```
Registration requires the wallet holder to be Orb-verified through World App.
### 3. Set your environment variables
```bash theme={null}
# Your wallet private key (used for both x402 payments and AgentKit signing)
PRIVATE_KEY=0x...
# Or use separate keys for payment and identity
X402_PRIVATE_KEY=0x... # USDC on Base: pays for sessions
AGENTKIT_PRIVATE_KEY=0x... # Registered in AgentBook: proves humanity
```
The same wallet can handle both x402 payments (USDC on Base) and AgentKit signing (identity on World Chain). They serve different purposes (payment vs. identity) but can share a key pair for convenience.
## Create a verified session
### Sign the AgentKit header
Your agent signs a [SIWE](https://eips.ethereum.org/EIPS/eip-4361)-formatted message before each request:
```typescript theme={null}
import { privateKeyToAccount } from "viem/accounts";
import { formatSIWEMessage } from "@worldcoin/agentkit";
import { randomBytes } from "crypto";
const account = privateKeyToAccount(
process.env.AGENTKIT_PRIVATE_KEY as `0x${string}`
);
async function signAgentkitHeader(url: string): Promise {
const parsed = new URL(url);
const now = new Date();
const expiry = new Date(now.getTime() + 5 * 60 * 1000); // 5-minute TTL
const info = {
domain: parsed.host,
uri: url,
version: "1" as const,
nonce: randomBytes(16).toString("hex"),
issuedAt: now.toISOString(),
expirationTime: expiry.toISOString(),
chainId: "eip155:480", // World Chain
type: "eip191" as const,
statement: "Verify your agent is backed by a real human",
};
const message = formatSIWEMessage(info, account.address);
const signature = await account.signMessage({ message });
const payload = { ...info, address: account.address, signature };
return Buffer.from(JSON.stringify(payload)).toString("base64");
}
```
### Send the request
Combine the `agentkit` header with your x402 payment using `x402-fetch`:
```typescript theme={null}
import { wrapFetchWithPayment } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";
const walletAccount = privateKeyToAccount(
process.env.PRIVATE_KEY as `0x${string}`
);
const x402Fetch = wrapFetchWithPayment(fetch, walletAccount);
const url = "https://x402.browserbase.com/browser/session/create";
const agentkitHeader = await signAgentkitHeader(url);
const session = await x402Fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
agentkit: agentkitHeader,
},
body: JSON.stringify({ estimatedMinutes: 30 }),
}).then((r) => r.json());
console.log(session.connectUrl);
// → wss://connect.browserbase.com/... (Verified browser)
```
### Connect with Playwright
```typescript theme={null}
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0].pages()[0];
await page.goto("https://example.com");
console.log(await page.title());
await browser.close();
```
## How verification works server-side
The x402 gateway verifies AgentKit proofs in four steps:
1. **Decode**: Base64-decode the `agentkit` header and parse the JSON payload
2. **Validate**: Check the SIWE message fields and TTL (must not be expired)
3. **Recover**: ECRECOVER the signer's address from the EIP-191 signature
4. **Lookup**: Query AgentBook on World Chain (`eip155:480`) to confirm the address belongs to a registered human
If any step fails, the gateway logs the reason and falls back to a standard browser. No error is returned to the client.
## FAQ
Yes. AgentBook registration requires the wallet holder to be verified through World App's Orb verification.
Yes. The x402 payment wallet (USDC on Base) and the AgentKit signing wallet (registered on World Chain) are independent. You can use the same wallet for both or separate them.
Nothing. The AgentKit proof is only checked at session creation time. Once your session is created as Verified, it stays that way for the session's lifetime.
No. The AgentKit proof is between your agent and the x402 gateway. Websites see a Verified Browserbase browser, they don't see your wallet address or World ID.
You still get a working browser session (you paid for it via x402). It just won't have Verified browser fingerprints. The gateway never rejects a paid request over a missing proof.
## Next steps
Detailed x402 payment flow with AgentKit premium unlock
All identity layers available to agents
# Agno integration
Source: https://docs.browserbase.com/integrations/agno/introduction
Utilize Browserbase with Agno to give your AI agents Browser Tools.
BrowserbaseTools from Agno enable an agent to automate browser interactions using Browserbase, a headless browser service.
## Key use cases
* **E-commerce**: Product prices, inventory, reviews
* **Social media**: Brand monitoring, engagement metrics
* **News and content**: Article aggregation, trend monitoring
* **Financial data**: Stock prices, market analysis
* **Research**: Academic papers, government records
## How it works
```
Your App → Browserbase API → Cloud Browser → Target Website → Data Back
```
**BrowserbaseTools** provides:
* Simple function calls for complex browser operations
* Automatic session management
* Intelligent error handling and retries
* Both sync and async operations
## Key concepts
### Sessions
A browser instance with its own state (cookies, history, storage)
### Connect URLs
Resume existing sessions or debug live browser instances
### Core functions
* `navigate_to`: Go to any URL
* `get_page_content`: Extract HTML content
* `screenshot`: Capture page visuals
* `close_session`: End browser session
## Technical capabilities
**JavaScript Execution**: Handle SPAs, AJAX, dynamic content\
**Visual Analysis**: Screenshots, layout detection, visual regression\
**Advanced Interactions**: Mouse, keyboard, forms, multi-step workflows\
**Smart Operations**: Wait for content, handle pagination, retry logic
## Best practices
**Performance**: Disable images when unneeded, use parallel sessions\
**Ethics**: Respect robots.txt, implement rate limiting\
**Error Handling**: Retry logic, session recovery, comprehensive logging\
**Security**: Secure API keys, validate data, use HTTPS
## Common integration patterns
### Agent-based architecture
BrowserbaseTools + AI agents enable:
* Natural language extraction commands
* Intelligent adaptation to page changes
* Automatic error recovery
* Context-aware data extraction
### Workflow automation
* **Data Processing**: Direct integration with pandas/NumPy
* **Storage**: Connect to databases and cloud storage
* **Notifications**: Email, Slack, webhook alerts
* **Scheduling**: Automated execution with cron/cloud schedulers
***
# Add web browsing capabilities to Agno
Source: https://docs.browserbase.com/integrations/agno/quickstart
Integrate Browserbase with Agno
Get up and running with data extraction in under 5 minutes.
## Setup
**Install packages:**
```shell theme={null}
pip install browserbase playwright agno
```
**Set environment variables:**
```shell theme={null}
export BROWSERBASE_API_KEY=your_api_key_here
```
To get env variables, go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input and set the `BROWSERBASE_API_KEY` environment variable.
## Basic example
```python theme={null}
from agno.agent import Agent
from agno.tools.browserbase import BrowserbaseTools
# Create extraction agent
agent = Agent(
name="Data Extractor",
tools=[BrowserbaseTools()],
instructions=[
"Extract content clearly and format nicely",
"Always close sessions when done"
],
markdown=True,
)
# Extract quotes
response = agent.run("""
Go to https://quotes.toscrape.com and:
1. Get the first 3 quotes with authors
2. Navigate to page 2
3. Get 2 more quotes from page 2
""")
print(response.content)
```
## Essential functions
| Function | Purpose | Usage |
| ------------------ | --------------- | -------------------------------------------------------- |
| `navigate_to` | Go to URL | "Navigate to [https://example.com](https://example.com)" |
| `get_page_content` | Extract HTML | "Get the page content" |
| `screenshot` | Take screenshot | "Take screenshot, save as 'page.png'" |
| `close_session` | End session | "Close the current session" |
# Anthropic Managed Agents
Source: https://docs.browserbase.com/integrations/anthropic/managed-agents/introduction
Run Claude agents in Anthropic's managed runtime and give them a Browserbase session to drive the web.
**Managed Agents** is Anthropic's hosted runtime for Claude. Anthropic provisions the container, the tool harness, and the agent loop. You supply an environment, credentials, a system prompt, and start a session. Pair a Managed Agent with a Browserbase session and Claude can navigate, click, and extract data on the open web without you hosting a browser fleet.
This integration connects the two surfaces:
* **Managed Agents** runs Claude in an Anthropic-managed sandbox with `bash`, file tools, and any custom tools you declare.
* **Browserbase** provides the isolated Chromium session the agent connects to over CDP, plus [Live View](/platform/browser/observability/session-live-view), [session recordings](/platform/browser/observability/session-recording), and [Agent Identity](/platform/identity/overview).
Managed Agents is in beta. All requests use the `managed-agents-2026-04-01` beta header, which the Anthropic SDKs set automatically. See [Anthropic's docs](https://docs.claude.com/en/docs/managed-agents/overview) for current status.
## Why pair them
Claude needs two things to browse the web reliably: a durable runtime to hold the loop, and a real browser to drive the page. Running either one yourself is painful.
* **Anthropic owns the runtime.** No Lambda cold starts, no container orchestration, no token streaming plumbing. Start a session and Anthropic runs the agent loop to completion.
* **Browserbase owns the browser.** No Chromium pinning, no fingerprint maintenance, no crashed-session cleanup. Every run gets a fresh, isolated sandbox with observability baked in.
* **The split keeps state clean.** Managed Agents holds the conversation and tool outputs. Browserbase holds the page, cookies, and downloads. You can inspect either side independently.
For the tradeoff against self-hosted Chromium, see [Managed vs self-hosted](/platform/browser/getting-started/remote-browser-versus-local-browser).
## Core concepts
Managed Agents is built around four primitives. You'll see them throughout this integration.
| Concept | Description |
| --------------- | -------------------------------------------------------------------------------------------- |
| **Agent** | The model, system prompt, tools, MCP servers, and skills. Created once and referenced by ID. |
| **Environment** | A configured container template: pre-installed packages, mounted files, network rules. |
| **Session** | A running agent instance inside an environment, performing a task and producing events. |
| **Events** | Messages exchanged between your app and the agent: user turns, tool results, status updates. |
## How it works
In the Anthropic console, create an environment with the `browse` npm package installed. Configure networking for the sites your agent needs to reach.
Create an environment-variable credential vault for `BROWSERBASE_API_KEY` so the agent can create and control Browserbase cloud sessions securely.
Create a Managed Agent with shell tools enabled and a system prompt that tells Claude to use the `browse` CLI for browser work.
Start a session from the Anthropic console or CLI. Anthropic provisions the sandbox, loads the environment and credentials, and gets ready to run the loop.
Inside the sandbox, Claude uses `browse` from the [Browse CLI](/integrations/skills/browse-cli) to create and connect to a Browserbase cloud session.
Claude issues `browse` commands against the session (`open`, `screenshot`, `snapshot`, `click`, `type`) over CDP. You watch progress in [Live View](/platform/browser/observability/session-live-view) or surface events back to your own UI through the session event stream.
When the agent goes idle, the session emits a `session.status_idle` event and Anthropic stops billing for compute. You end the Browserbase session (or let it time out) and surface the answer to the user.
## What the stack looks like
| Layer | Owner | Role |
| ---------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------- |
| Agent loop, tool execution, conversation state | Anthropic Managed Agents | Runs Claude to completion in a sandbox |
| `bash`, file tools, custom tools | Anthropic Managed Agents | Built-in toolset, plus any custom tool you declare |
| [`browse` CLI](https://www.npmjs.com/package/browse) | Environment package (runs inside the sandbox) | Translates Claude's intent into Browserbase/CDP actions |
| Chromium session, Live View, recordings | Browserbase | Isolated browser with observability and Agent Identity |
## When to use this
Use Managed Agents with Browserbase when:
* You want Claude to browse the web but don't want to host the agent loop yourself.
* You need persistent conversation state across many tool calls (Slack bots, long research tasks, queued jobs).
* You want Anthropic-side tool-use traces for debugging alongside Browserbase [session recordings](/platform/browser/observability/session-recording).
* You're building a chat surface (Slack, Discord, a web app) where each thread is a long-running agent session with steering.
* You're already building a general-purpose Managed Agent and want to add web browsing as another capability alongside its existing tools.
Use a lighter setup (Stagehand or direct Messages API calls) when the task is one-shot, latency-sensitive, or you need full control over every tool call.
## Getting started
Create a Managed Agent in the Anthropic console and give it Browserbase cloud browsing.
The lightweight entry point Claude uses inside the sandbox to control Browserbase.
## Further reading
Why managed Browserbase sessions beat self-hosted Chromium in production.
Watch a Managed Agent drive a browser in real time.
Verified browsers, proxies, and authentication for agents on the open web.
# Browsers for Managed Agents
Source: https://docs.browserbase.com/integrations/anthropic/managed-agents/quickstart
Hand your Anthropic Managed Agents a full cloud browser
This guide shows how to equip your Managed Agents with browser, search, and fetch tooling to navigate the web. The [`browse` CLI](https://www.npmjs.com/package/browse) powers your agents to run UI testing, conduct deep document research, fill web forms, and complex browser automations.
One CLI for skills, browser primitives, debugging, and cloud sessions, designed to be driven by AI Agents.
## 1. Create an environment
Create an environment with the `browse` npm package installed. For open-web browser agents, set networking to unrestricted. Use limited networking when you want to scope the agent to specific websites.
In the Anthropic Console, create a new Managed Agents environment. Add `browse` as a default npm package and set networking to **Unrestricted**.
Use `ant beta:environments create`. The `--config` flag accepts structured literals; `ant beta:environments create --help` shows the available flags.
```bash theme={null}
ENVIRONMENT_ID=$(ant beta:environments create \
--name "browserbase-env" \
--config '{type: cloud, packages: {type: packages, npm: [browse]}, networking: {type: unrestricted}}' \
--transform id --raw-output)
```
```typescript theme={null}
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const environment = await client.beta.environments.create({
name: "browserbase-env",
config: {
type: "cloud",
packages: {
type: "packages",
npm: ["browse"],
},
networking: { type: "unrestricted" },
},
});
console.log(`MANAGED_ENVIRONMENT_ID=${environment.id}`);
```
The [`browse` CLI](https://www.npmjs.com/package/browse) is designed for agents to inspect and operate webpages. It exposes commands like `browse click`, `browse snapshot`, `browse screenshot`, and `browse network`, and can drive local Chromium, any CDP connection, or Browserbase cloud sessions.
## 2. Create a credential vault
Create a credential vault so the agent can securely read your Browserbase API key without hardcoding it in prompts.
Create a vault that will hold an environment-variable credential.
Choose **Environment variable** as the credential vault type.
Create the vault first. You will add the environment-variable credential in the next step.
```bash theme={null}
VAULT_ID=$(ant beta:vaults create \
--display-name "Browserbase" \
--metadata '{service: browserbase}' \
--transform id --raw-output)
```
```typescript theme={null}
const vault = await client.beta.vaults.create({
display_name: "Browserbase",
metadata: {
service: "browserbase",
},
});
console.log(`MANAGED_VAULT_ID=${vault.id}`);
```
Name the variable `BROWSERBASE_API_KEY` and set its value to your Browserbase API key. Use limited networking with `*.browserbase.com` as the allowed host.
Add `BROWSERBASE_API_KEY`, paste your Browserbase API key, and choose **Limited** networking with `*.browserbase.com`.
`ant beta:vaults:credentials create --help` exposes the credential command. The environment-variable credential carries the variable name, secret value, and allowed hosts.
```bash theme={null}
ant beta:vaults:credentials create \
--vault-id "$VAULT_ID" \
--display-name "BROWSERBASE_API_KEY" \
--auth "{type: environment_variable, name: BROWSERBASE_API_KEY, value: \"$BROWSERBASE_API_KEY\", allowed_hosts: [\"*.browserbase.com\"]}"
```
```typescript theme={null}
await client.beta.vaults.credentials.create(vault.id, {
display_name: "BROWSERBASE_API_KEY",
auth: {
type: "environment_variable",
name: "BROWSERBASE_API_KEY",
value: process.env.BROWSERBASE_API_KEY!,
allowed_hosts: ["*.browserbase.com"],
},
});
```
If your SDK types do not yet include environment-variable credentials, use the Console or CLI path for this vault step and keep the rest of the SDK flow unchanged.
## 3. Create the agent
Create a Managed Agent that knows `browse` is available in the environment. Keep the system prompt short and let the CLI provide command details through `browse --help`.
```yaml theme={null}
name: browser agent
model:
id: claude-opus-4-8
speed: standard
description: Runs shell commands to check CLI tools and environment variables.
system: You are a browser agent. Use the `browse` CLI to operate a headless Chromium environment. Run `browse --help` to view the commands interface.
mcp_servers: []
tools:
- configs:
- name: web_fetch
enabled: false
- name: web_search
enabled: false
default_config:
enabled: true
permission_policy:
type: always_allow
type: agent_toolset_20260401
skills: []
metadata: {}
```
You can use this .yml to configure your agent. Optionally disable native `web_fetch` and `web_search` to harness Browserbase's Fetch and Search APIs via the `browse` CLI.
Configure the agent with the browser-agent system prompt and shell tool access.
Use `ant beta:agents create`. The `agent_toolset_20260401` toolset gives the agent shell access, and the environment supplies `browse`.
```bash theme={null}
AGENT_ID=$(ant beta:agents create \
--name "browser agent" \
--model '{id: claude-opus-4-8, speed: standard}' \
--description "Runs shell commands to check CLI tools and environment variables." \
--system 'You are a browser agent. Use the `browse` CLI to operate a headless Chromium environment. Run `browse --help` to view the commands interface.' \
--tool '{"type":"agent_toolset_20260401","configs":[{"name":"web_fetch","enabled":false},{"name":"web_search","enabled":false}],"default_config":{"enabled":true,"permission_policy":{"type":"always_allow"}}}' \
--transform id --raw-output)
```
```typescript theme={null}
const SYSTEM_PROMPT =
"You are a browser agent. Use the `browse` CLI to operate a headless Chromium environment. Run `browse --help` to view the commands interface.";
const agent = await client.beta.agents.create({
name: "browser agent",
model: {
id: "claude-opus-4-8",
speed: "standard",
},
description:
"Runs shell commands to check CLI tools and environment variables.",
system: SYSTEM_PROMPT,
tools: [
{
type: "agent_toolset_20260401",
configs: [
{ name: "web_fetch", enabled: false },
{ name: "web_search", enabled: false },
],
default_config: {
enabled: true,
permission_policy: { type: "always_allow" },
},
},
],
});
console.log(`MANAGED_AGENT_ID=${agent.id}`);
```
## 4. Start a session
Start a Managed Agents session, attach the environment and vault, then ask the agent to complete a browser task such as:
```text theme={null}
Please find the price of the Fellow Kettle on Target.com and add it to cart.
```
```text theme={null}
Find driving traffic time from SFO to Union Square.
```
```text theme={null}
Please run a full E2E test for UI regressions on this preview URL: https://example.com
```
```text theme={null}
Please check if Adam Smith is a licensed accountant in California.
```
Start a session from the Anthropic Console with the agent, environment, and Browserbase credential vault you created.
Create a session with `ant beta:sessions create`, then send a user event. Open the stream in a second terminal if you want to watch events as they arrive.
```bash theme={null}
SESSION_ID=$(ant beta:sessions create \
--agent "$AGENT_ID" \
--environment-id "$ENVIRONMENT_ID" \
--vault-id "$VAULT_ID" \
--title "Browserbase quickstart" \
--transform id --raw-output)
```
```bash theme={null}
ant beta:sessions:events stream \
--session-id "$SESSION_ID" \
--max-items -1
```
```bash theme={null}
ant beta:sessions:events send \
--session-id "$SESSION_ID" \
--event '{type: user.message, content: [{type: text, text: "Please find the price of the Fellow Kettle on Target.com and add it to cart."}]}'
```
```typescript theme={null}
const session = await client.beta.sessions.create({
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Browserbase quickstart",
});
const stream = await client.beta.sessions.events.stream(session.id);
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Please find the price of the Fellow Kettle on Target.com and add it to cart.",
},
],
},
],
});
for await (const event of stream) {
if (event.type === "agent.message") {
for (const block of event.content) {
process.stdout.write(block.text);
}
} else if (event.type === "agent.tool_use") {
console.log(`\n[Using tool: ${event.name}]`);
} else if (event.type === "session.status_idle") {
console.log("\n\nAgent finished.");
break;
}
}
```
## 5. Watch the browser session
When the Managed Agent uses Browserbase through `browse`, the browser session appears in the [Browserbase sessions panel](https://browserbase.com/sessions). Open the session to watch Live View, inspect logs, and review the recording after the run.
## Tips
Ask it to run `browse --help` first. The CLI is designed for progressive disclosure, so the agent can inspect available commands before acting.
Confirm the credential vault exposes `BROWSERBASE_API_KEY` to the environment, and make sure the vault networking allows `*.browserbase.com`.
Use **Unrestricted** networking on the environment. Use **Limited** networking only when you know the exact hostnames the agent needs.
Use Browserbase Live View while the session is running, then use the session recording and logs in the Browserbase dashboard after it finishes.
# Box integration
Source: https://docs.browserbase.com/integrations/box/introduction
Send files from the web to Box, extract structured data with Box AI, and route documents for review.
Browserbase and Box connect browser agents to document workflows. Browserbase runs browsers that work where APIs can't, while Box stores files and extracts the information your agent needs.
The example integration uses Stagehand to download a safety data sheet (SDS) and sample pesticide label from the web. It uploads both PDFs to Box, extracts compliance fields, and decides whether the packet needs human review.
## What the integration does
Stagehand finds and downloads PDF and image files in a Browserbase session.
The workflow uploads each file to a folder owned by a Box service account.
Box AI extracts fields, confidence scores, and source references. Structured extraction can apply OCR to images and scanned PDFs.
A deterministic check compares the extracted fields and marks each file as `APPROVED` or `NEEDS_REVIEW`.
## How it works
1. Stagehand opens the manufacturer and government source pages in a Browserbase browser.
2. The browser downloads the SDS and label PDFs to Browserbase.
3. The workflow retrieves the original file bytes through the Browserbase Downloads API.
4. Box stores both files and answers a cited safety question about the SDS.
5. Box AI extracts structured fields from each file, including text embedded in the label artwork.
6. The workflow compares the EPA registration numbers and checks the SDS revision date.
7. Box metadata records the extracted values, Browserbase session ID, source URL, document role, and compliance status.
The default sources intentionally produce a packet that needs review. You can replace the source URLs and link names to process your own SDS and label pair.
Box AI processes the SDS and label separately. The workflow compares their structured results in application code because Box AI doesn't process a mixed text-and-image packet as one request.
## What you'll need
* A [Browserbase API key](https://www.browserbase.com/settings)
* A Box Server app that uses Client Credentials Grant
* A Box plan with access to the Box AI API
* A Box folder shared with the app's service account
* Node.js 18 or newer and pnpm
## Get started
Configure Browserbase and Box, then run the compliance intake.
Review the complete Stagehand and Box implementation.
# Build a Box compliance intake
Source: https://docs.browserbase.com/integrations/box/quickstart
Download documents with Stagehand, analyze them with Box AI, and route compliance exceptions for review.
This guide runs a complete document intake. Stagehand downloads an SDS and sample pesticide-label PDF in a Browserbase session. Box stores the files, extracts their compliance fields, and records the review decision.
Copy your API key from [Browserbase settings](https://www.browserbase.com/settings).
Open the [Box Developer Console](https://app.box.com/developers/console), then create a **Server Authentication** app with **Client Credentials Grant**.
Enable these application scopes:
* Read all files and folders stored in Box
* Write all files and folders stored in Box
* Manage AI
Authorize the app in your Box enterprise. Copy the client ID, client secret, and enterprise ID for the next step.
Client Credentials Grant creates short-lived access tokens for the app's service account at runtime. You don't need an interactive login or a Box Developer Token.
Find the service account email in the Box app details. Invite that account to the destination folder with the **Editor** role.
Copy the folder ID from its Box URL:
```text theme={null}
https://app.box.com/folder/123456789
└──────── BOX_FOLDER_ID
```
The workflow can access only the content available to this service account.
Clone the integrations repository and install the Box example:
```bash theme={null}
git clone https://github.com/browserbase/integrations.git
cd integrations/examples/integrations/box
pnpm install --ignore-workspace
```
Create your local environment file:
```bash theme={null}
cp .env.example .env
```
Add your Browserbase and Box credentials:
```env .env theme={null}
BROWSERBASE_API_KEY=your_browserbase_api_key
BOX_CLIENT_ID=your_box_client_id
BOX_CLIENT_SECRET=your_box_client_secret
BOX_ENTERPRISE_ID=your_box_enterprise_id
BOX_FOLDER_ID=your_box_folder_id
```
The example includes default SDS and label sources. Override them when you want to process a different pair:
```env .env theme={null}
SDS_PAGE_URL=https://example.com/safety-data-sheet
SDS_LINK_TEXT=Download safety data sheet
LABEL_PAGE_URL=https://example.com/product-label
LABEL_LINK_TEXT=Download product label
```
Each source link must start a browser download. Stagehand clicks the link, and the Browserbase Downloads API exposes the resulting file.
Start the workflow:
```bash theme={null}
pnpm start
```
The command prints:
* A Browserbase Session Inspector URL
* The cited Box AI answer
* Extracted metadata, confidence scores, and source references
* The `APPROVED` or `NEEDS_REVIEW` decision
* Links to the uploaded Box files
## Review the workflow
The example uses Stagehand to find and click each document link. Browserbase stores the remote browser downloads, and the workflow retrieves the original bytes through the Downloads API.
After it uploads the files, the workflow calls Box AI to:
* Answer a safety question about the SDS with citations
* Extract structured SDS fields
* Extract label fields, including text embedded in the label artwork
* Return confidence scores and references for review
The compliance check normalizes the EPA registration numbers, compares them, and confirms that the SDS includes a revision date. Any missing or conflicting value produces `NEEDS_REVIEW`.
Finally, the workflow adds these values to each file's global `properties` metadata:
* Extracted compliance fields
* Browserbase session ID
* Source URL
* Document role
* Compliance status
## Troubleshooting
### Box AI isn't ready
Box may need time to index a new file. The example retries readiness, rate-limit, and server responses with bounded exponential backoff before it stops.
### The browser opens a file instead of downloading it
Confirm that the source link starts a browser download. Update the source URL and accessible link name in `.env`, then use the Session Inspector to confirm that Stagehand clicks the expected link.
### Box returns a permissions error
Confirm that the enterprise authorized the Server app, the app has the required file and Box AI scopes, and the destination folder grants its service account the Editor role.
## Next steps
Learn how to retrieve files from Browserbase sessions.
Build browser agents with Stagehand and Browserbase.
Review the Box API fields, OCR support, and response format.
Configure unattended authentication for a Box service account.
# Braintrust integration
Source: https://docs.browserbase.com/integrations/braintrust/introduction
Utilize Browserbase with Braintrust for browser agent capabilities.
Braintrust is a platform for building AI applications, making it more efficient for Large Language Models (LLMs) to become more robust, reliable, and interactive.
Some features include prototyping with different prompts and LLMs in a sandboxed environment, real-time monitoring and performance insights, and data management through an intuitive UI.
Most Large Language Models support tool calling, which allows you to define tools with well-defined input and output types. They're commonly used for two purposes:
1. To enable models to "call" tools that perform external tasks, and then use those results to produce a final response
2. To coerce a model into producing structured outputs that match a given JSON schema
Braintrust supports both use cases, including [using prompts in your code](https://www.braintrust.dev/docs/guides/prompts#using-prompts-in-your-code) which can be executed directly using TypeScript or Python. [Custom functions](https://www.braintrust.dev/docs/reference/functions), a broader concept in Braintrust, enhance the capabilities of LLMs by allowing you to:
* Add web-browsing capabilities (using tools like Browserbase)
* Perform complex computations
* Access external APIs
## Add Browserbase to Braintrust
Configure Browserbase to add additional web-browsing capabilities to
Braintrust tool calling.
# Add web browsing capabilities to Braintrust
Source: https://docs.browserbase.com/integrations/braintrust/typescript
Integrate Browserbase with Braintrust
Go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input and
set the `BROWSERBASE_API_KEY` environment variable.
Create a new [Braintrust
organization](https://docs.braintrust.dev/getting-started/creating-a-project)
if you haven't already.
In addition to Braintrust and Browserbase SDK, you'll also use [Zod](https://zod.dev/) for parameter validation.
```bash theme={null}
npm install zod braintrust playwright-core
```
Set the `BROWSERBASE_API_KEY` environment variable in Braintrust. This lets you use the Browserbase SDK to fetch page content.
In addition to setting the `BROWSERBASE_API_KEY` environment variable, you'll also need to upload your AI API keys to Braintrust.
Braintrust supports a variety of AI providers, so you can select the one that best fits your needs. You can set the AI provider in the Braintrust Settings page.
Your existing code in Braintrust works well with Browserbase as a tool.
Tools can be created through code. Define a load function that fetches and returns the page content from a given URL.
Here's how to modify your existing code to use Browserbase as a tool in Braintrust:
```typescript browserbase.ts theme={null}
import * as braintrust from "braintrust";
import { z } from "zod";
import { chromium } from "playwright-core";
// Create a session with Browserbase
async function createSession() {
const response = await fetch(`https://api.browserbase.com/v1/sessions`, {
method: "POST",
headers: {
"x-bb-api-key": `${process.env.BROWSERBASE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
proxies: true,
}),
});
const json = await response.json();
return json;
}
// Load page from the internet
async function loadPage({ url }: { url: string }) {
const { id } = await createSession();
const browser = await chromium.connectOverCDP(
`wss://connect.browserbase.com?apiKey=${process.env.BROWSERBASE_API_KEY}&sessionId=${id}`,
);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await page.goto(url);
const readable: { title?: string; textContent?: string } =
await page.evaluate(`
import('https://cdn.skypack.dev/@mozilla/readability').then(readability => {
return new readability.Readability(document).parse()
})`);
const text = `${readable.title}\n${readable.textContent}`;
return { page: text };
}
// Create a new project and tool in Braintrust
const project = braintrust.projects.create({ name: "Browserbase API Tool" });
project.tools.create({
handler: loadPage,
parameters: z.object({
url: z.string(),
}),
returns: z.object({
page: z.string(),
}),
name: "Load page",
slug: "load-page",
description: "Load a page from the internet",
ifExists: "replace",
});
```
Make sure you set your environment variables `BRAINTRUST_API_KEY` and
`BROWSERBASE_API_KEY` locally before proceeding.
Use the Braintrust CLI to push the function to Braintrust. This deploys the function and makes it available in your project.
Once the command completes, you should see the function listed in the Library's "Tools" tab. You can now input a URL and use the "Load page" tool to fetch the page content.
```bash theme={null}
npx braintrust push browserbase.ts
```
You've successfully integrated Browserbase with Braintrust! You can use tools to create simple and composable agents that perform tasks like data extraction, retrieval augmented generation (RAG), API execution, and much more. Learn more about prompting and tool calling in [Braintrust Docs](https://www.braintrust.dev/docs/guides/prompts).
Go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input and
set the `BROWSERBASE_API_KEY` environment variable.
Create a new [Braintrust
organization](https://docs.braintrust.dev/getting-started/creating-a-project)
if you haven't already.
In addition to Braintrust and Browserbase SDK, you'll also use [Zod](https://zod.dev/) for parameter validation.
```bash theme={null}
npm install zod braintrust @browserbasehq/sdk playwright-core
```
Set the `BROWSERBASE_API_KEY` environment variable in Braintrust. This lets you use the Browserbase SDK to fetch page content.
In addition to setting the `BROWSERBASE_API_KEY` environment variable, you'll also need to upload your AI API keys to Braintrust.
Braintrust supports a variety of AI providers, so you can select the one that best fits your needs. You can set the AI provider in the Braintrust Settings page.
Your existing code in Braintrust works well with Browserbase as a tool.
Tools can be created through code. Define a load function that fetches and returns the page content from a given URL.
Here's how to modify your existing code to use Browserbase as a tool in Braintrust:
```typescript browserbase.ts theme={null}
import * as braintrust from "braintrust";
import Browserbase from "@browserbasehq/sdk";
import { chromium } from "playwright-core";
import { z } from "zod";
const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;
async function load({ url }: { url: string }): Promise<{
page: string;
}> {
const bb = new Browserbase({
apiKey: BROWSERBASE_API_KEY,
});
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0];
await page.goto(url);
const text = await page.textContent("body");
return { page: text || "" };
}
const project = braintrust.projects.create({ name: "browse test" });
project.tools.create({
handler: load,
parameters: z.object({
url: z.string(),
}),
returns: z.object({
page: z.string(),
}),
name: "Load page",
slug: "load-page",
description: "Load a page from the internet",
ifExists: "replace",
});
```
Make sure you set your environment variables `BRAINTRUST_API_KEY` and
`BROWSERBASE_API_KEY` locally before proceeding.
Use the Braintrust CLI to push the function to Braintrust. This deploys the function and makes it available in your project.
Once the command completes, you should see the function listed in the Library's "Tools" tab. You can now input a URL and use the "Load page" tool to fetch the page content.
```bash theme={null}
npx braintrust push browserbase.ts
```
You've successfully integrated Browserbase with Braintrust! You can use tools to create simple and composable agents that perform tasks like data extraction, retrieval augmented generation (RAG), API execution, and much more. Learn more about prompting and tool calling in [Braintrust Docs](https://www.braintrust.dev/docs/guides/prompts).
# Browser Use integration
Source: https://docs.browserbase.com/integrations/browseruse/introduction
Utilize Browserbase with Browser Use for browser agent capabilities.
Browser Use is a browser automation SDK that uses screenshots to capture the state of the browser and actions to simulate user interactions.
Browser Use currently only supports Python.
For TypeScript, you can use Stagehand instead. [Stagehand](https://stagehand.dev/) is the SDK for browser agents. It lets you use natural language to interact with the web.
Check out the docs [here](https://docs.stagehand.dev/v3/first-steps/introduction).
## Add Browserbase to Browser Use
Configure Browserbase to add additional web-browsing capabilities to your
Browser Use.
Stagehand is the SDK for browser agents, use natural language to interact with the web.
Check out the docs [here](https://docs.stagehand.dev/v3/first-steps/introduction).
# Add web browsing capabilities to Browser Use
Source: https://docs.browserbase.com/integrations/browseruse/python
Integrate Browserbase with Browser Use
Visit the [Dashboard's Overview tab](https://www.browserbase.com/overview):
Then copy and set the `BROWSERBASE_API_KEY` environment variable in your `.env` file.
[UV](https://docs.astral.sh/uv/getting-started/installation/) is a modern package manager for Python.
```bash theme={null}
uv venv
```
```bash theme={null}
source .venv/bin/activate # follow the `uv venv` output
uv pip install browserbase browser-use python-dotenv
```
Browser Use provides a simple `Browser` class that accepts a CDP URL from Browserbase. Create a `main.py` file:
```python main.py theme={null}
import asyncio
import os
from dotenv import load_dotenv
from browserbase import Browserbase
from browser_use import Agent, Browser, BrowserProfile, ChatAnthropic
load_dotenv()
async def main():
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
print(f"Session ID: {session.id}")
print(f"Debug URL: https://www.browserbase.com/sessions/{session.id}")
browser = Browser(
browser_profile=BrowserProfile(cdp_url=session.connect_url)
)
llm = ChatAnthropic(model="claude-sonnet-4-6")
agent = Agent(
task=(
"Go to https://www.macrumors.com/contact.php and fill in the form. "
"Make sure to use the selectors and submit the form"
),
llm=llm,
browser=browser,
)
result = await agent.run()
print(f"Result: {result}")
await browser.stop()
if __name__ == "__main__":
asyncio.run(main())
```
Run your script:
```bash theme={null}
uv run main.py
```
You should see your Browserbase session start in [Browserbase](https://www.browserbase.com/sessions). The debug URL prints to the console for real-time session monitoring.
**Important environment variables**
Make sure you have these environment variables in your `.env` file:
* `BROWSERBASE_API_KEY`
* `ANTHROPIC_API_KEY` (or another LLM provider key)
**Notes**
* The `BrowserProfile(cdp_url=...)` connects to your Browserbase session via CDP
* Browser Use handles browser lifecycle, Playwright management, and cleanup automatically
* You can configure the LLM by passing a `llm` parameter to `Agent` (e.g. `ChatAnthropic`, `ChatOpenAI`)
* Reach out at [support@browserbase.com](mailto:support@browserbase.com) for additional support
Configure Browserbase to add additional web-browsing capabilities to your
Browser Use.
# Tutorial: build a flight booking Crew
Source: https://docs.browserbase.com/integrations/crew-ai/build-a-flight-booker
Build a Crew that finds the best roundtrip flights on the given dates.
Flight booking is tricky because most airlines don't offer public APIs. You usually need to simulate human interactions with web interfaces.
With [CrewAI](https://www.crewai.com/) and [Browserbase](https://www.browserbase.com/), you can automate this in a few dozen lines of code.
By following this tutorial, you'll learn how to build a CrewAI program that searches for a roundtrip flight from a simple human input:
```text theme={null}
> python3 main.py "San Francisco to NYC one-way on September 21st"
Here are our top 5 flights from San Francisco (SFO) to Newark (EWR) on September 21, 2024:
1. **Alaska Airlines**:
- Departure: 8:50 am
- Arrival: 5:24 pm
- Duration: 5 hours 34 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [Alaska Airlines Saver](https://www.kayak.com/book/flight?code=noAiOYx8xU.4fFBlTtfVpoDzQq2dWkU9A.12411.28f6c8a3257adb48c2f7d8207660b2a0&h=41a638bca25d&_kw_pbranded=true&sub=F-1450586051791345216E0040d85ce85&pageOrigin=F..RP.FE.M4)
...
```
## Introduction: Crews, Agents, Tasks, and Tools
[CrewAI](https://www.crewai.com/) helps developers build AI Agents with 4 core concepts:
[Crews](https://docs.crewai.com/core-concepts/Crews/), [Agents](https://docs.crewai.com/core-concepts/Agents/),
[Tasks](https://docs.crewai.com/core-concepts/Tasks/), and [Tools](https://docs.crewai.com/core-concepts/Tools/):
* A `Crew` is a team of `Agents` working together to accomplish some tasks.
* A `Task`, such as *"Search flights according to criteria"*, is a goal assigned to a specialized `Agent` (e.g., *a Flight Booking Agent*).
* An `Agent` can be seen as a specialized text-only GPT that receives a set of `Tools` to perform actions (e.g., *search on Google*, *navigate to this URL*).
### Example
Here is an example of a Crew assembled to research a given topic and write an article.
**The Agents: A Researcher and a Writer**
First, define 2 Agents, one specialized in researching a topic and another in writing articles:
```py theme={null}
researcher = Agent(
role='Senior Researcher',
goal='Uncover groundbreaking technologies in {topic}',
backstory=(
"Driven by curiosity, you're at the forefront of"
"innovation, eager to explore and share knowledge that could change"
"the world."
),
tools=[search_tool],
)
writer = Agent(
role='Writer',
goal='Narrate compelling tech stories about {topic}',
backstory=(
"With a flair for simplifying complex topics, you craft"
"engaging narratives that captivate and educate, bringing new"
"discoveries to light in an accessible manner."
),
tools=[search_tool]
)
```
Each Agent gets:
* a `role` that helps the `Crew` select the best Agent for a given `Task`.
* a `goal` that frames the `Agent` decision-making process when iterating on a `Task`.
* a `backstory` providing context to the `Agent`'s `role` and `goal`.
Both Agents get access to a `search_tool` ([`SerperDevTool` instance](https://docs.crewai.com/tools/SerperDevTool/)) to perform searches with Google Search.
**The Tasks: writing and researching**
Now define 2 tasks: researching a topic and writing an article.
```py theme={null}
research_task = Task(
description=(
"Identify the next big trend in {topic}."
"Focus on identifying pros and cons and the overall narrative."
"Your final report should clearly articulate the key points,"
"its market opportunities, and potential risks."
),
expected_output='A comprehensive 3 paragraphs long report on the latest AI trends.',
agent=researcher,
)
write_task = Task(
description=(
"Compose an insightful article on {topic}."
"Focus on the latest trends and how it's impacting the industry."
"This article should be easy to understand, engaging, and positive."
),
expected_output='A 4 paragraph article on {topic} advancements formatted as markdown.',
agent=writer,
output_file='new-blog-post.md' # Example of output customization
)
```
A Task's `description` can be compared to a prompt, while the `expected_output` helps format the result of the `Task`.
As expected, the `write_task` gets assigned to the `writer` Agent and the `research_task` to the `researcher` Agent.
**Agents and Tasks look very similar: do I need both?**
Indeed, in a simple example as this one, the `Agent` and `Task` look alike. In real-world applications, an `Agent` gets to
perform multiple tasks. Then, an `Agent` represents the expertise (`goal`, `backstory`) with a set of skills (`tools`), while a `Task` is a goal to accomplish.
**Assembling the Crew**
As covered earlier, a `Crew` defines a set of `Task` to be performed sequentially by a team of `Agents`.
Note that `Tasks` share a context, explaining why the research task comes before the writing task.
```py theme={null}
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
memory=True,
cache=True,
max_rpm=100,
)
result = crew.kickoff(inputs={'topic': 'AI in healthcare'})
print(result)
```
Build the Flight Booking Crew with these concepts!
***
## 1. The Flight Booking Crew
Before jumping into the setup and code, step back and look at how to assemble a Crew that helps book flights.
From a user input like *"San Francisco to New York one-way on 21st September"*, the Flight Booking Crew should print the top 5 flights as follows:
```bash theme={null}
Here are our top 5 picks from San Francisco to New York on 21st September 2024:
1. **Delta Airlines**
- Departure: 21:35
- Arrival: 03:50
- Duration: 6 hours 15 minutes
- Layovers: Direct
- Price: $125
- Booking: [Delta Airlines](https://www.kayak.com/flights/sfo/jfk/2024-09-21/12:45/13:55/2:10/delta/airlines/economy/1)
...
```
To achieve this goal, the Crew will navigate to [https://www.kayak.com](https://www.kayak.com), perform a search, and extract each flight detail, which translates to the following steps:
1. Parse the user request (*"San Francisco to New York one-way on 21st September"*) to build a valid *Kayak search URL*
2. Navigate to the *Kayak search URL* and extract the top 5 flights
3. For each flight, navigate to the *flight details URL* to extract the available *providers* (airlines)
4. Summarize the flights' information
To perform those steps, you'll create 2 Agents:
* The "Flights" Agent, responsible for looking for flights
* The "Summarize" Agent, responsible for summarizing the available flights as a comprehensive list
The "Search Flights" Agent will need:
* A custom `Kayak` tool to translate the user input into a valid *Kayak search URL*
* A Browserbase tool to navigate on Kayak and interact with the web page
Finally, you'll define 2 tasks: *"Search Flights"* and *"Search Booking Providers"*.
You can visualize the Flight Booking Crew as follows:
Implement the Crew!
## 2. Installation
Set up the project by installing the required dependencies:
```bash theme={null}
pip install crewai 'crewai[tools]' html2text playwright python-dotenv
```
Create a `.env` file with the following variables and their respective values:
```bash .env theme={null}
OPENAI_API_KEY=
BROWSERBASE_API_KEY=
# our Flight Booking's "Search Flights" Agent will have to load a lot of context (heavy webpages as text),
# let's configure a specific OpenAI model to avoid token size limits:
OPENAI_MODEL_NAME=gpt-4.1
```
**Where can I find my OpenAI and Browserbase API Keys?**
* Get your Browserbase API Key from your [Settings page](https://www.browserbase.com/settings).
* Get your OpenAI API Key from the [OpenAI Platform](https://platform.openai.com/api-keys).
## 3. Create the tools
While CrewAI provides [a wide range of tools](https://docs.crewai.com/core-concepts/Tools/#available-crewai-tools) (e.g., *the [SerperDevTool](https://docs.crewai.com/tools/SerperDevTool/) to perform searches with Google Search*), the *"Search Flights"* Agent needs 2 custom tools:
* a custom `Kayak` tool to assemble a valid *Kayak search URL*
* a Browserbase loader to navigate and interact with the web pages
### The Browserbase tool
The Kayak website relies heavily on JavaScript and performs a live flight search, making it hard to interact with:
Fortunately, leveraging Browserbase's headless browsers makes loading and interacting with such websites easier while benefiting from its [Verified](/platform/identity/overview).
Look at the custom Browserbase Tool implementation:
```py browserbase.py theme={null}
import os
from crewai_tools import tool
from playwright.sync_api import sync_playwright
from html2text import html2text
from time import sleep
@tool("Browserbase tool")
def browserbase(url: str):
"""
Loads a URL using a headless webbrowser
:param url: The URL to load
:return: The text content of the page
"""
with sync_playwright() as playwright:
browser = playwright.chromium.connect_over_cdp(
"wss://connect.browserbase.com?apiKey="
+ os.environ["BROWSERBASE_API_KEY"]
)
context = browser.contexts[0]
page = context.pages[0]
page.goto(url)
# Wait for the flight search to finish
sleep(25)
content = html2text(page.content())
browser.close()
return content
```
**Custom Tool definition**
A custom `Tool` is composed of 3 elements:
* a name, via the `@tool("name")` decorator
* a description defining the purpose of the tool along with its parameters
* a function that contains the tool's logic
The description, provided as a multi-line comment, is used by the Agents to evaluate the best-fitted `Tool` to help complete a given `Task`.
A description can also provide instructions on the parameters. Here, the unique `url` parameter is instructed to be a URL.
**Browserbase Tool Logic**
The Browserbase tool utilizes the `playwright` library along with the Browserbase Connect API to initiate a headless browser session. This setup allows interaction with web pages as follows:
```py theme={null}
browser = playwright.chromium.connect_over_cdp(
"wss://connect.browserbase.com?apiKey="
+ os.environ["BROWSERBASE_API_KEY"]
)
```
Then, it leverages the `html2text` library to convert the webpage's content to text and return it to the Agent for processing.
### The Kayak tool
Agents are capable of reasoning but cannot build a valid *Kayak search URL* from the ground up.
To help the *"Flights"* Agent, here's a simple *Kayak Tool*:
```py kayak.py theme={null}
from crewai_tools import tool
from typing import Optional
@tool("Kayak tool")
def kayak(
departure: str, destination: str, date: str, return_date: Optional[str] = None
) -> str:
"""
Generates a Kayak URL for flights between departure and destination on the specified date.
:param departure: The IATA code for the departure airport (e.g., 'SOF' for Sofia)
:param destination: The IATA code for the destination airport (e.g., 'BER' for Berlin)
:param date: The date of the flight in the format 'YYYY-MM-DD'
:return_date: Only for two-way tickets. The date of return flight in the format 'YYYY-MM-DD'
:return: The Kayak URL for the flight search
"""
print(f"Generating Kayak URL for {departure} to {destination} on {date}")
URL = f"https://www.kayak.com/flights/{departure}-{destination}/{date}"
if return_date:
URL += f"/{return_date}"
URL += "?currency=USD"
return URL
```
The Kayak tool describes multiple parameters with specific format instructions.
For example: `date: The date of the flight in the format 'YYYY-MM-DD'`
This illustrates the flexibility of Tools that can rely on the `Agents` powerful reasoning capabilities to solve formatting challenges that generally require some preprocessing.
## 4. Set up the agents
The *Flights* Agent now has the tools to navigate the Kayak website from a high-level user input (*"San Francisco to New York one-way on 21st September"*).
Set up the 2 Agents:
```py main.py theme={null}
from crewai import Agent
# import our tools
from browserbase import browserbase
from kayak import kayak
flights_agent = Agent(
role="Flights",
goal="Search flights",
backstory="I am an agent that can search for flights.",
tools=[kayak, browserbase],
allow_delegation=False,
)
summarize_agent = Agent(
role="Summarize",
goal="Summarize content",
backstory="I am an agent that can summarize text.",
allow_delegation=False,
)
```
As outlined in the introduction, an `Agent` needs 3 properties: a `role`, a `goal`, and a `backstory`.
The role of these two Agents is to orchestrate the tools (build the URL, then navigate to it) and
extract the information from the webpages' text. For this reason, their definition is straightforward.
**What is the role of the *Summarize* Agent?**
Through iterations building this Flight Booker, the Crew with a single *Flights* Agent
was struggling to distinguish flights from flight providers (booking links).
The Summarize Agent, as the next section covers, isn't assigned to any task.
It is created and assigned to the Crew to help digest the text extracted from the web pages and distinguish the flights from the providers (booking links).
## 4. Define the tasks
Define the core part of the Flight Booking Crew, the `Tasks`.
From a given flight criteria, the Crew should print the 5 first available flights with their associated booking link.
To achieve this, the Crew needs to:
1. Navigate to the *Kayak search URL* and extract the top 5 flights
2. For each flight, navigate to the *flight details URL* to extract the available *providers* and booking links
### The "Search flights" Task
The *Search flights* Task is bound to the *Flights* Agent, getting access to the custom tools:
```py main.py theme={null}
from crewai import Task
# Agents definitions...
output_search_example = """
Here are our top 5 flights from San Francisco to New York on 21st September 2024:
1. Delta Airlines: Departure: 21:35, Arrival: 03:50, Duration: 6 hours 15 minutes, Price: $125, Details: https://www.kayak.com/flights/sfo/jfk/2024-09-21/12:45/13:55/2:10/delta/airlines/economy/1
"""
search_task = Task(
description=(
"Search flights according to criteria {request}. Current year: {current_year}"
),
expected_output=output_search_example,
agent=flights_agent,
)
```
The `description` will be provided to the *Flights* Agent who will call:
1. The Kayak Tool to build a valid *Kayak search URL*
2. Then, leverage the Browserbase Tool to get the flight results as text
3. Finally, using the `output_search_example` and with the help of the *Summarize* Agent, it will return a list of 5 flights
**Why provide the `current_year`?**
Most users will prompt a relative date, for example: *"San Francisco to New York one-way on 21st September"*.
An Agent's reasoning relies on OpenAI, which lacks some intuition on relative dates ([OpenAI will always think it's 2022](https://community.openai.com/t/api-doesnt-know-date-thinks-its-2022/569125)).
For this reason, you need to specify the current year in the prompt (Task's `description`).
### The "Search Booking Providers" Task
The *Search Booking Providers* Task relies heavily on the `Agent` reasoning capabilities:
```py main.py theme={null}
from crewai import Task
# Agents definitions...
output_providers_example = """
Here are our top 5 picks from San Francisco to New York on 21st September 2024:
1. Delta Airlines:
- Departure: 21:35
- Arrival: 03:50
- Duration: 6 hours 15 minutes
- Price: $125
- Booking: [Delta Airlines](https://www.kayak.com/flights/sfo/jfk/2024-09-21/12:45/13:55/2:10/delta/airlines/economy/1)
...
"""
search_booking_providers_task = Task(
description="Load every flight individually and find available booking providers",
expected_output=output_providers_example,
agent=flights_agent,
)
```
By asking to *"Load every flight individually"*, the *Flights* Agent will understand that it needs
to locate a URL to navigate to for each flight result.
The *Search Booking Providers* will indirectly rely on the *Summarize* Agent to consolidate the flights result and individual flight providers' results as showcased in `output_providers_example`.
## 4. Assemble the Flight Booking Crew
It's time to assemble the Crew by arranging the `Task` in the correct order (*search flights, then gather providers and booking links*):
```py main.py theme={null}
import sys
import datetime
from crewai import Crew, Process, Task, Agent
from browserbase import browserbase
from kayak import kayak
from dotenv import load_dotenv
load_dotenv() # take environment variables from .env.
# Tasks and Agents definitions...
crew = Crew(
agents=[flights_agent, summarize_agent],
tasks=[search_task, search_booking_providers_task],
# let's cap the number of OpenAI requests as the Agents
# may have to do multiple costly calls with large context
max_rpm=100,
# let's also set verbose=True and planning=True
# to see the progress of the Agents
# and the Task execution. Remove these lines
# if you want to run the script without
# seeing the progress (like in production).
verbose=True,
planning=True,
)
result = crew.kickoff(
inputs={
"request": sys.argv[1],
"current_year": datetime.date.today().year,
}
)
print(result)
```
The Crew must complete the *Search Flight* task followed by the *Search Booking Providers* task.
As covered earlier, the *Summarize* Agent gets assigned to the `Crew` - *not to a `Task`* - to help consolidate the flights and providers into a simple list.
**Let the Crew *kick off!***
A `Crew` process starts by calling the `kickoff()` method.
The Crew needs 2 inputs: the user input (*"San Francisco to New York one-way on 21st September"*) and the current year.
## 5. Running a flight booking search
The CrewAI program is now complete!
Try it: Look at its execution steps in detail.
### Running the program
**OpenAI cost**
Expect each run of the program to cost around \$0.50 OpenAI credits.
The Agent reasoning relies heavily on OpenAI and sends large chunks of text (the webpages), resulting in significant contexts (\~50k context tokens per run).
Search for a one-way flight from New York to San Francisco by running:
```bash theme={null}
python3 main.py "San Francisco to New York one-way on 21st September"
```
As the program starts running in verbose mode, you should see some logs stream in your terminal; take a closer look at the steps.
### A close look at the Crew steps
Looking at the debugging logs streamed to the terminal helps you understand how the Crew works.
Explore the logs in the following steps:
```
[DEBUG]: == Working Agent: Flights
[INFO]: == Starting Task: Search flights according to criteria San Francisco to New York one-way on 21st September. Current year: 2024
> Entering new CrewAgentExecutor chain...
Thought: I need to generate a URL using the Kayak tool for the flight search from San Francisco (SFO) to New York (JFK) on 21st September 2024.
```
You can already see the magic of the *Flights* Agent reasoning in action.
Given the Task definition and the 2 tools available, the *Flights* Agent concludes *"I need to generate a URL using the Kayak tool for the flight search"*.
```
Action: Kayak tool
Action Input: {"departure": "SFO", "destination": "JFK", "date": "2024-09-21"}
https://www.kayak.com/flights/SFO-JFK/2024-09-21
Thought: Now that I have the URL, I need to load it using the Browserbase tool to retrieve the flight information.
```
The *Action Input* shows that the *Flights* Agent successfully parsed the user input as valid parameters.
Once the URL is generated, the Agent immediately reaches the next step: fetching the flight list using the URL.
```
Action: Browserbase tool
Action Input: {"url": "https://www.kayak.com/flights/SFO-JFK/2024-09-21"}
[Kayak](/)
......
Thought: I now know the final answer
Final Answer: Here are our top 5 flights from San Francisco (SFO) to Newark (EWR) on September 21, 2024:
Here are our top 5 flights from San Francisco (SFO) to Newark (EWR) on September 21, 2024:
1. **Alaska Airlines**:
- Departure: 8:50 am
- Arrival: 5:24 pm
- Duration: 5 hours 34 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [Alaska Airlines Saver](https://www.kayak.com/book/flight?code=noAiOYx8xU.4fFBlTtfVpoDzQq2dWkU9A.12411.28f6c8a3257adb48c2f7d8207660b2a0&h=41a638bca25d&_kw_pbranded=true&sub=F-1450586051791345216E0040d85ce85&pageOrigin=F..RP.FE.M4)
2. **United Airlines**:
- Departure: 1:30 pm
- Arrival: 9:50 pm
- Duration: 5 hours 20 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.df899b8e44c813d2f8c5501a1648fc15&h=3e1b76440249&sub=F-5023348394153941183E0bc6c2fafa5&pageOrigin=F..RP.FE.M1)
3. **United Airlines**:
- Departure: 4:40 pm
- Arrival: 1:13 am+1
- Duration: 5 hours 33 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.5ec6fd14128fd0c540fd0f53d711947a&h=f6ae82999387&sub=F-5023348393135040028E0bc6c2fafa5&pageOrigin=F..RP.FE.M6)
4. **United Airlines**:
- Departure: 11:59 pm
- Arrival: 8:27 am+1
- Duration: 5 hours 28 minutes
- Layovers: Nonstop
- Price: $144
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.14383.65a16596bc682cce98ddcd39666710a3&h=e34e775c0ed7&sub=F-5023348391216069073E0bc6c2fafa5&pageOrigin=F..RP.FE.M9)
5. **United Airlines**:
- Departure: 7:15 am
- Arrival: 3:30 pm
- Duration: 5 hours 15 minutes
- Layovers: Nonstop
- Price: $159
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.15888.f2fb6ff5bafca7eed4751036a9b91597&h=7ce06a5da162&sub=F-5023348394219198114E0bc6c2fafa5&pageOrigin=F..RP.FE.M10)
> Finished chain.
```
In this step, *Flights* Agent retrieves the Kayak webpage as text and leverages OpenAI to extract a flight list.
This is the program's slowest and most costly action, as OpenAI takes up to multiple minutes to process the request.
Once the flight list is generated, the Crew marks the first `Task` ("Search for flights") as completed (*"Finished chain."*) and moves to the next one.
The second `Task` is impressive as the `Agent` realizes that it needs to loop over the 5 flights to retrieve the booking provider:
```
[DEBUG]: == Working Agent: Flights
[INFO]: == Starting Task: Load every flight individually and find available booking providers
> Entering new CrewAgentExecutor chain...
I need to load each of the provided flight detail URLs using the Browserbase tool to gather the detailed information necessary to present the top 5 picks from San Francisco to New York on 21st September 2024.
Action: Browserbase tool
Action Input: {"url": "https://www.kayak.com/flights/SFO-JFK/2024-09-21"}
[Kayak](/)
......
Thought:
Now that I have gathered the necessary details from the Delta Airlines flight URL, I need to process the same for the remaining four flights from San Francisco to New York on 21st September 2024.
```
Once the booking links of each flight has been retrieved, the Agent completes a final step by summarizing the list:
```
Thought: Here are our top 5 flights from San Francisco (SFO) to Newark (EWR) on September 21, 2024:
1. **Alaska Airlines**:
- Departure: 8:50 am
- Arrival: 5:24 pm
- Duration: 5 hours 34 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [Alaska Airlines Saver](https://www.kayak.com/book/flight?code=noAiOYx8xU.4fFBlTtfVpoDzQq2dWkU9A.12411.28f6c8a3257adb48c2f7d8207660b2a0&h=41a638bca25d&_kw_pbranded=true&sub=F-1450586051791345216E0040d85ce85&pageOrigin=F..RP.FE.M4)
2. **United Airlines**:
- Departure: 1:30 pm
- Arrival: 9:50 pm
- Duration: 5 hours 20 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.df899b8e44c813d2f8c5501a1648fc15&h=3e1b76440249&sub=F-5023348394153941183E0bc6c2fafa5&pageOrigin=F..RP.FE.M1)
3. **United Airlines**:
- Departure: 4:40 pm
- Arrival: 1:13 am+1
- Duration: 5 hours 33 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.5ec6fd14128fd0c540fd0f53d711947a&h=f6ae82999387&sub=F-5023348393135040028E0bc6c2fafa5&pageOrigin=F..RP.FE.M6)
4. **United Airlines**:
- Departure: 11:59 pm
- Arrival: 8:27 am+1
- Duration: 5 hours 28 minutes
- Layovers: Nonstop
- Price: $144
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.14383.65a16596bc682cce98ddcd39666710a3&h=e34e775c0ed7&sub=F-5023348391216069073E0bc6c2fafa5&pageOrigin=F..RP.FE.M9)
5. **United Airlines**:
- Departure: 7:15 am
- Arrival: 3:30 pm
- Duration: 5 hours 15 minutes
- Layovers: Nonstop
- Price: $159
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.15888.f2fb6ff5bafca7eed4751036a9b91597&h=7ce06a5da162&sub=F-5023348394219198114E0bc6c2fafa5&pageOrigin=F..RP.FE.M10)
> Finished chain.
```
Once finished, the program prints the final answer returned by the `Crew`:
```
Here are our top 5 flights from San Francisco (SFO) to Newark (EWR) on September 21, 2024:
1. **Alaska Airlines**:
- Departure: 8:50 am
- Arrival: 5:24 pm
- Duration: 5 hours 34 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [Alaska Airlines Saver](https://www.kayak.com/book/flight?code=noAiOYx8xU.4fFBlTtfVpoDzQq2dWkU9A.12411.28f6c8a3257adb48c2f7d8207660b2a0&h=41a638bca25d&_kw_pbranded=true&sub=F-1450586051791345216E0040d85ce85&pageOrigin=F..RP.FE.M4)
2. **United Airlines**:
- Departure: 1:30 pm
- Arrival: 9:50 pm
- Duration: 5 hours 20 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.df899b8e44c813d2f8c5501a1648fc15&h=3e1b76440249&sub=F-5023348394153941183E0bc6c2fafa5&pageOrigin=F..RP.FE.M1)
3. **United Airlines**:
- Departure: 4:40 pm
- Arrival: 1:13 am+1
- Duration: 5 hours 33 minutes
- Layovers: Nonstop
- Price: $125
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.12448.5ec6fd14128fd0c540fd0f53d711947a&h=f6ae82999387&sub=F-5023348393135040028E0bc6c2fafa5&pageOrigin=F..RP.FE.M6)
4. **United Airlines**:
- Departure: 11:59 pm
- Arrival: 8:27 am+1
- Duration: 5 hours 28 minutes
- Layovers: Nonstop
- Price: $144
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.14383.65a16596bc682cce98ddcd39666710a3&h=e34e775c0ed7&sub=F-5023348391216069073E0bc6c2fafa5&pageOrigin=F..RP.FE.M9)
5. **United Airlines**:
- Departure: 7:15 am
- Arrival: 3:30 pm
- Duration: 5 hours 15 minutes
- Layovers: Nonstop
- Price: $159
- Booking: [United Airlines Basic Economy](https://www.kayak.com/book/flight?code=noAiOYx8xU.UYIuDTZHiSY.15888.f2fb6ff5bafca7eed4751036a9b91597&h=7ce06a5da162&sub=F-5023348394219198114E0bc6c2fafa5&pageOrigin=F..RP.FE.M10)
```
## Wrapping up
CrewAI provides a powerful way to develop AI Agents. The traditional approach of Prompt Engineering is replaced by instructions that leverage the `Agent`'s reasoning capabilities.
As this example shows, Agents can complete `Tasks` defined with high-level instructions (ex: *"Load every flight individually and find available booking providers"*)
Combined with Browserbase headless browsers, crewAI helps create powerful AI Agents that automate human tasks or access data not available through public APIs.
Check out the repo!
# crewAI integration
Source: https://docs.browserbase.com/integrations/crew-ai/introduction
Let your crewAI Agent browse the web like a human.
One of the most advanced use cases for LLMs is AI Agents, programs designed to perform tasks based on high-level instructions.
[crewAI](https://www.crewai.com/) is a framework to create Crews, teams of Agents working together on a given problem. For example, searching for a flight:
AI Agents rely on tools to gather rich contexts and perform actions to achieve such results.
Browserbase provides a [BrowserbaseLoadTool](https://docs.crewai.com/tools/BrowserbaseLoadTool/) that Agents can use to retrieve context from complex webpages such as:
* Extract text from webpages using JavaScript or bot protection systems
* Capture images from webpages
## Get started with Browserbase for crewAI
Enable your crewAI Agents to browse the web like humans.
Combine crewAI and Browserbase to build a Flight Booking Crew.
# Add web browsing capabilities to your agent
Source: https://docs.browserbase.com/integrations/crew-ai/python
Let your crewAI Agent browse the web like a human.
Go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input and
set the `BROWSERBASE_API_KEY` environment variable.
```bash theme={null}
pip install browserbase 'crewai[tools]'
```
```python theme={null}
from crewai_tools import BrowserbaseLoadTool
from crewai import Agent
# See https://github.com/joaomdmoura/crewAI-examples/blob/main/instagram_post/tools/search_tools.py
from tools.search_tools import SearchTools
browserbase_tool = BrowserbaseLoadTool()
# Extract the text from the site
text = browserbase_tool.run()
print(text)
# Use the BrowserbaseLoadTool for travel planning
agent = Agent(
role='Local Expert at this city',
goal='Provide the BEST insights about the selected city',
backstory="""A knowledgeable local guide with extensive information
about the city, it's attractions and customs""",
tools=[
SearchTools.search_internet,
browserbase_tool,
],
verbose=True
)
```
# Get started with integrations
Source: https://docs.browserbase.com/integrations/get-started
Use your favorite technologies seamlessly with Browserbase.
Integrate Browserbase with your preferred platforms, APIs, and services, from AI agents to workflow automation.
Use the [integration request form](https://www.browserbase.com/partner) to submit your own integration or request a new one.
### Prerequisites
Before you start, make sure you have:
* An active Browserbase account with your [API key](https://www.browserbase.com). Follow the [getting started guide](https://docs.browserbase.com/welcome/getting-started) to create your project and generate your API key if you don't have one yet.
* Authentication credentials for your chosen third-party service (such as Braintrust API key or Stripe API key)
* Working knowledge of Browserbase's core concepts and your preferred integration framework (Stagehand, Playwright, Puppeteer, etc.)
## Integration quick links
Supercharge your evaluations with sophisticated web browsing capabilities.
Streamline online payments and virtual credit card management with precision.
Enable your CrewAI agents to navigate the web with human-like interactions and extractions.
Pay-per-use browser sessions with crypto. No API keys, no accounts. Just pay and connect.
AI-powered browser automation in your n8n workflows. Just provide a URL and an instruction.
Connect OpenClaw to Browserbase with the built-in browser plugin first, or use the Browserbase CLI interface.
## Need more help?
Visit the [Support Portal](https://www.browserbase.com/contact) or explore the [Blog](https://www.browserbase.com/blog) for the latest updates, in-depth tutorials, and implementation best practices.
## Can't find the integration you want?
Submit your own integration or request a new one via the [integration request form](https://www.browserbase.com/partner).
# Google ADK integration
Source: https://docs.browserbase.com/integrations/google-adk/introduction
Give Google ADK agents a browser with Browserbase and Stagehand
The [Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/) is a flexible and modular framework for developing and deploying AI agents. While optimized for Gemini and the Google ecosystem, ADK is **model-agnostic**, **deployment-agnostic**, and built for **compatibility with other frameworks**. This integration shows how to use the Browserbase MCP Server with ADK to build agents that can interact with web pages using natural language commands.
## Key features
Empower your agent to perform multi-step tasks like logging into websites, filling out forms, submitting data, and navigating complex user flows
Automatically browse to specific pages and extract structured data, text
content, or other information for use in your agent's tasks
Capture full-page or element-specific screenshots to visually monitor
websites, test UI elements, or feed visual context back to a vision-enabled
model
Interact with web pages using natural language commands powered by Stagehand's AI primitives, making web automation more context-efficient and reliable.
## Getting started
Get started with Google ADK by following the setup guide
Ready to get started? Check out the [setup
guide](/integrations/google-adk/setup) for detailed installation instructions.
## Further reading
* [ADK Quickstart Guide](https://google.github.io/adk-docs/get-started/)
* [Browserbase MCP Server](/integrations/mcp/setup)
* [Stagehand](https://stagehand.dev) - The SDK for browser agents
# Google ADK setup
Source: https://docs.browserbase.com/integrations/google-adk/setup
Configure Browserbase MCP Server for Google Agent Development Kit
The Browserbase MCP Server can be integrated with Google ADK agents to provide browser automation capabilities. Browserbase supports both local STDIO and hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) (SHTTP) transport methods.
[Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
with Browserbase's remote hosted URL is recommended to take advantage of the server at full capacity
and avoid managing local processes.
## Prerequisites
Get your Browserbase API key from the [Browserbase Dashboard](https://www.browserbase.com/overview).
Then copy your API Key directly from the input.
Get your Gemini API key from [Google AI Studio](https://aistudio.google.com/apikey) for AI-powered browser automation with Stagehand. Only required if you're using the local MCP server.
## Setup methods
**Recommended**: When using the remote hosted server, Browserbase covers the LLM costs for Gemini, the [best performing model](https://www.stagehand.dev/evals) in [Stagehand](https://www.stagehand.dev).
```python theme={null}
from google.adk.agents import Agent
from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
root_agent = Agent(
model="gemini-2.5-pro",
name="browserbase_agent",
instruction="Help users get information from web pages using Browserbase",
tools=[
McpToolset(
connection_params=SseConnectionParams(
url=(
"https://mcp.browserbase.com/mcp"
"?browserbaseApiKey=YOUR_BROWSERBASE_API_KEY"
),
timeout=300,
),
)
],
)
```
For local development or when you need more control over the server configuration, use the STDIO transport method.
```python theme={null}
from google.adk.agents import Agent
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
from mcp import StdioServerParameters
BROWSERBASE_API_KEY = "YOUR_BROWSERBASE_API_KEY"
GEMINI_API_KEY = "YOUR_GEMINI_API_KEY"
root_agent = Agent(
model="gemini-2.5-pro",
name="browserbase_agent",
instruction="Help users get information from web pages using Browserbase",
tools=[
McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="npx",
args=[
"-y",
"@browserbasehq/mcp",
],
env={
"BROWSERBASE_API_KEY": BROWSERBASE_API_KEY,
"GEMINI_API_KEY": GEMINI_API_KEY,
}
),
timeout=300,
),
)
],
)
```
When using STDIO, you'll need to provide your own Gemini API key and will incur LLM costs for Stagehand operations.
## Optional runtime query params
You can append these query parameters to the MCP URL:
* `keepAlive=true|false`
* `proxies=true|false`
* `verified=true|false`
## Verify installation
Test your integration by running your agent:
```python theme={null}
# Run your agent
result = root_agent.run("Navigate to google.com and take a screenshot")
print(result)
```
Monitor your browser sessions in real-time on the [Browserbase
Dashboard](https://www.browserbase.com/sessions).
## Available tools
Once configured, your Google ADK agent will have access to these Browserbase tools:
* **start**: Create or reuse a Browserbase session
* **end**: Close the current Browserbase session
* **navigate**: Navigate to a URL
* **act**: Perform actions using natural language
* **observe**: Find actionable elements on a page
* **extract**: Extract data from a page
See [MCP Setup](/integrations/mcp/setup) for tool details.
## Configuration reference
For full query parameter behavior (required/optional fields, model behavior, and validation), see [MCP Setup](/integrations/mcp/setup).
## Additional resources
* [Google ADK Documentation](https://google.github.io/adk-docs/)
* [ADK Quickstart Guide](https://google.github.io/adk-docs/get-started/)
* [Stagehand](https://stagehand.dev)
# Hermes Agent integration
Source: https://docs.browserbase.com/integrations/hermes-agent/introduction
Connect Browserbase to Hermes Agent using Hermes' built-in Browserbase cloud mode or the Browse CLI.
[Hermes Agent](https://hermes-agent.nousresearch.com/docs) includes built-in browser automation with multiple backend options, including Browserbase cloud mode.
For Browserbase users, the recommended Hermes integration path is Hermes' built-in Browserbase browser support. If you want access to additional Browserbase platform features and more granular control over browser sessions, use the Browse CLI path second.
## Recommended order
Recommended. Use Hermes' native browser tooling with Browserbase as the cloud browser provider.
Use this when you want additional Browserbase platform features and more granular control over sessions, contexts, logs, fetch, search, and browser configuration.
## Setup paths
| Order | Path | Best for |
| ----- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| 1 | [Built-in Browserbase setup](/integrations/hermes-agent/setup) | Users who want Hermes' native browser commands to run on Browserbase |
| 2 | [Browse CLI](/integrations/skills/browserbase-cli) | Users who want `browse` commands for sessions, projects, functions, fetch, and search |
## Common use cases
* Run Hermes browser tasks on Browserbase instead of a local Chromium install
* Give Hermes access to Browserbase sessions with managed cloud browsers, residential proxies, and anti-bot tooling
* Keep Hermes' native browser workflow while moving browser execution to Browserbase infrastructure
## Next step
Start with the recommended Hermes Browserbase cloud mode configuration.
Use the Browse CLI if your workflow is centered on `browse`.
# Hermes Agent setup
Source: https://docs.browserbase.com/integrations/hermes-agent/setup
Set up Hermes Agent with Browserbase, starting with Hermes' built-in Browserbase cloud mode and then the Browse CLI.
This guide recommends two Browserbase setup paths for Hermes Agent, in this order:
1. Use Hermes' built-in Browserbase cloud mode.
2. Use the [Browse CLI](/integrations/skills/browserbase-cli) if you specifically want `browse` workflows.
## Recommended: Hermes Browserbase cloud mode
This is the preferred path because Hermes already supports Browserbase as a built-in cloud browser provider.
Create a Browserbase account, then copy your API key from the [Overview dashboard](https://www.browserbase.com/overview).
Add the following to `~/.hermes/.env`:
```bash theme={null}
BROWSERBASE_API_KEY=your_browserbase_api_key
```
If you have not enabled browser tools yet, run:
```bash theme={null}
hermes tools
```
Then enable **Browser Automation** in the Hermes setup flow.
Once configured, Hermes can use its native browser toolset against Browserbase-managed cloud browsers.
Example prompts:
```text theme={null}
Open https://example.com and summarize the page
Navigate to https://news.ycombinator.com and extract the top 5 story titles
```
According to the current Hermes browser docs, if you set both Browserbase and Browser Use credentials, Browserbase takes priority as the cloud browser provider.
## Helpful Browserbase settings
Hermes also supports Browserbase-specific environment variables for common cloud browser behavior:
```bash theme={null}
# Residential proxies for better CAPTCHA solving
BROWSERBASE_PROXIES=true
# Keep sessions alive across disconnects on supported plans
BROWSERBASE_KEEP_ALIVE=true
# Override session timeout in milliseconds
BROWSERBASE_SESSION_TIMEOUT=600000
```
## Alternative: Browse CLI
If your workflow is centered on `browse` commands instead of Hermes' native browser tools, use the Browse CLI.
```bash theme={null}
npm i -g browse
```
```bash theme={null}
export BROWSERBASE_API_KEY="your_api_key"
```
Example commands:
```bash theme={null}
browse projects list
browse sessions get
browse fetch https://example.com
browse search "browser automation" --num-results 5
```
For the full CLI workflow reference, see the [Browse CLI guide](/integrations/skills/browserbase-cli).
# Introduction
Source: https://docs.browserbase.com/integrations/ibm/introduction
Track real-time mortgage rates using IBM watsonx.ai
By the end of this guide, you'll be able to capture screenshots of current mortgage rates and analyze them with vision capabilities.
You'll learn how to:
1. Capture real-time mortgage data using Browserbase's headless browser automation.
2. Analyze visual data with IBM [watsonx.ai](http://watsonx.ai)'s vision language models to extract meaningful information.
This integration is useful for:
* Accessing data embedded within iframes that's difficult to extract with traditional methods
* Automating financial data collection from websites without structured APIs
* Converting visual mortgage rate data into structured information
* Building financial monitoring tools that track rate changes over time
## Prerequisites
Before you start, make sure you have:
* [IBM Watson Project ID & API key](https://cloud.ibm.com/catalog/services/watson-studio)
* Access to IBM's foundation models including vision capabilities
* [Browserbase API key](https://browserbase.com/settings/)
* Python environment with required dependencies
**Note:** This guide is only available in Python
## Why screenshots?
Traditional data extraction methods often fail when dealing with:
* Content embedded within iframes (like the Freddie Mac mortgage rates)
* Data rendered by JavaScript after page load
* Complex interactive visualizations
* Protected or bot-blocked content
Browserbase captures full screenshots of rendered pages, and IBM [watsonx.ai](http://watsonx.ai) interprets the visual content, letting you extract information that's otherwise inaccessible through HTML parsing or API calls.
## Quickstart guides
Follow the **quickstart guide** to capture mortgage rates and analyze them with IBM Watson's visual language models.
Explore enterprise solutions for large-scale financial data monitoring with **Browserbase and IBM Watson**.
# Mortgage rate capture
Source: https://docs.browserbase.com/integrations/ibm/quickstart
This guide shows how to use Browserbase with IBM watsonx.ai to create a mortgage rate analysis tool. The integration captures screenshots of mortgage rate data from Freddie Mac's website and uses a vision model to analyze and summarize the rates.
Here are the steps:
1. Set up your environment
2. Capture a web page screenshot
3. Analyze the screenshot with IBM watsonx.ai
4. Create a mortgage rate report
## Build a mortgage rate analysis tool
### 1. Set up your environment
Install the required dependencies and set up your API keys.
```bash pip theme={null}
pip install browserbase playwright ibm-watsonx-ai python-dotenv
```
```bash poetry theme={null}
poetry add browserbase playwright ibm-watsonx-ai python-dotenv
```
```bash uv theme={null}
uv pip install browserbase playwright ibm-watsonx-ai python-dotenv
```
```bash conda theme={null}
conda install -c conda-forge browserbase playwright ibm-watsonx-ai python-dotenv
```
Create a `.env` file with your API keys:
* IBM watsonx.ai [API key and Project ID](https://www.ibm.com/watsonx/developer/get-started/quick-start/)
* Browserbase [API key](https://browserbase.com/settings/)
```env .env theme={null}
IBM_API_KEY=your_ibm_api_key
IBM_PROJECT_ID=your_ibm_project_id
BROWSERBASE_API_KEY=your_browserbase_api_key
```
Make sure to install Playwright dependencies after installation by running `playwright install` in your terminal.
### 2. Capture a web page screenshot
Use Browserbase to navigate to the Freddie Mac mortgage rates page and capture a screenshot of the rate information that's embedded in an iframe:
```python screenshot.py theme={null}
from playwright.sync_api import sync_playwright
from browserbase import Browserbase
import os
import base64
from dotenv import load_dotenv
load_dotenv()
def capture_mortgage_rates():
# Create BrowserBase session and take screenshot
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as playwright:
# Setup browser
browser = playwright.chromium.connect_over_cdp(session.connect_url)
context = browser.new_context()
page = context.new_page()
# Navigate to Freddie Mac's mortgage rates page
page.goto("https://www.freddiemac.com/pmms")
# Navigate and screenshot
page.locator("#main-content iframe").content_frame.get_by_role(
"heading", name="Primary Mortgage Market"
).scroll_into_view_if_needed()
# Take a screenshot via CDP for better performance
client = context.new_cdp_session(page)
screenshot_data = client.send("Page.captureScreenshot", {
"format": "jpeg",
"quality": 80,
"fullpage": True
})
# Save the screenshot
image_data = base64.b64decode(screenshot_data['data'])
with open('freddie_mac_rates.jpeg', 'wb') as f:
f.write(image_data)
# Clean up
browser.close()
return 'freddie_mac_rates.jpeg'
if __name__ == "__main__":
screenshot_path = capture_mortgage_rates()
print(f"Screenshot saved as '{screenshot_path}'")
```
### 3. Analyze the screenshot with IBM watsonx.ai
Now, use IBM watsonx.ai's vision model to analyze the captured screenshot and extract the mortgage rate information:
```python analyze.py theme={null}
from ibm_watsonx_ai import APIClient, Credentials
from ibm_watsonx_ai.foundation_models import ModelInference
import base64
import os
import datetime
from dotenv import load_dotenv
load_dotenv()
def get_image_base64(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
def analyze_mortgage_rates(image_path):
# Get base64 encoded image
image_base64 = get_image_base64(image_path)
# Set up IBM watsonx.ai
credentials = Credentials(
# use your IBM URL based on region
url="https://us-south.ml.cloud.ibm.com",
api_key=os.getenv("IBM_API_KEY")
)
client = APIClient(credentials)
project_id = os.getenv("IBM_PROJECT_ID")
print(f"Using IBM watsonx.ai project ID: {project_id}")
# Initialize the Llama 3 vision model
model = ModelInference(
model_id="meta-llama/llama-3-2-90b-vision-instruct",
api_client=client,
project_id=project_id,
params={
"max_new_tokens": 1000,
"time_limit": 10000
}
)
# Prepare prompt with the image
messages = [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
},
{
"type": "text",
"text": "Please provide a clear summary of the current mortgage rates shown in this Freddie Mac PMMS page. Format the response as a brief market update, including the date and the rates for 30-year and 15-year fixed mortgages."
}
]
}
]
# Generate the analysis
print("Analyzing mortgage rates with IBM watsonx.ai...")
response = model.chat(
messages=messages,
params={
"input_type": "chat"
}
)
# Extract content from response
if isinstance(response, dict):
content = response.get('choices', [{}])[0].get('message', {}).get('content', '')
else:
# Some API versions return the content directly
content = response
return content
if __name__ == "__main__":
analysis = analyze_mortgage_rates('freddie_mac_rates.jpeg')
```
### 4. Create a mortgage rate report
Combine the screenshot capture and analysis steps into a complete solution that produces a nicely formatted report.
```python mortgage_analysis.py theme={null}
from screenshot import capture_mortgage_rates
from analyze import analyze_mortgage_rates
def main():
# Capture the screenshot
print("Step 1: Capturing screenshot of mortgage rates...")
screenshot_path = capture_mortgage_rates()
# Analyze the rates
print("\nStep 2: Analyzing image...")
analysis = analyze_mortgage_rates(screenshot_path)
# Display the results
print("\nMortgage Rate Analysis:")
print(analysis)
if __name__ == "__main__":
main()
```
Example output:
```
Step 1: Capturing screenshot of mortgage rates...
Screenshot saved as 'freddie_mac_rates.jpeg'
Step 2: Analyzing image...
Using project_id: proj-123abc456def
Analyzing mortgage rates with IBM watsonx.ai...
Generating response...
=== MORTGAGE RATE SUMMARY ===
--------------------------------------------------
Mortgage Market Update - March 20, 2025
According to the Freddie Mac Primary Mortgage Market Survey (PMMS), the 30-year fixed-rate mortgage averaged 6.70% this week.
The 15-year fixed-rate mortgage averaged 5.95% this week.
These rates reflect the latest trends in the mortgage market as of March 20, 2025.
--------------------------------------------------
```
🎉 You've created a mortgage rate analysis tool with **IBM watsonx.ai** and **Browserbase**!
## Next steps
With this foundation, you can build more advanced workflows:
* Schedule regular rate checks and track trends over time
* Compare rates across multiple mortgage providers
* Create dashboards to monitor rate changes and predict future trends
* Fill out mortgage applications using extracted data
## Best practices
* **Handle dynamic content**: Some websites load data asynchronously, so make sure content is fully loaded by adding wait times before capturing screenshots.
* **Add error handling**: Implement robust error handling for network issues and page structure changes.
* **Secure your credentials**: Never expose API keys in client-side code or public repositories.
For more information, explore:
* [IBM watsonx.ai documentation](https://www.ibm.com/products/watsonx-ai)
* [Browserbase documentation](https://docs.browserbase.com/)
# Using Inngest with Browserbase
Source: https://docs.browserbase.com/integrations/inngest/introduction
Develop AI agents that can browse the web autonomously using Browserbase and Inngest AgentKit.
Inngest's [AgentKit](https://docs.agentkit.ai/getting-started) lets you build AI agents that browse the web using Browserbase's managed headless browsers. Pair it with [Stagehand](https://www.stagehand.dev/) for resilient, autonomous browsing.
You'll learn how to:
1. **Create AgentKit tools** that use Browserbase sessions
2. **Build autonomous browsing agents** that search, extract data, and interact with websites
3. **Add Stagehand** for resilient browser automation
## Two approaches to using Browserbase with AgentKit
1. **Create custom Browserbase tools**: Ideal for simple actions on webpages with manual browser control.
2. **Use Stagehand library as tools**: Better for autonomous browsing and resilient data extraction.
## Prerequisites
Before you start, make sure you have:
* [AgentKit installed](https://docs.agentkit.ai/getting-started)
* [Browserbase API key](https://browserbase.com/settings/)
* *(Optional)* LLM API key of your choice to use with [Stagehand](https://www.stagehand.dev/)
Next, set up your first web browsing agent with AgentKit and Browserbase.
## Quickstart guides
Build autonomous browsing agents with Stagehand that navigate, extract data, and interact with sites.
A complete Reddit search agent built with AgentKit and Browserbase.
# Reddit search agent
Source: https://docs.browserbase.com/integrations/inngest/quickstart
Enable your AI agents to search Reddit with Browserbase's managed headless browsers.
## 1. Install AgentKit
Install AgentKit, Browserbase, and Playwright core:
```bash theme={null}
npm install @inngest/agent-kit @browserbasehq/sdk playwright-core
```
```bash theme={null}
pnpm add @inngest/agent-kit @browserbasehq/sdk playwright-core
```
```bash theme={null}
yarn add @inngest/agent-kit @browserbasehq/sdk playwright-core
```
Don't have an existing project? Create a new one with your preferred package manager before continuing.
## 2. Set up an AgentKit network with an agent
Create an agent and its associated network, for example a Reddit search agent:
```typescript theme={null}
import {
anthropic,
createAgent,
createNetwork,
} from "@inngest/agent-kit";
const searchAgent = createAgent({
name: "reddit_searcher",
description: "An agent that searches Reddit for relevant information",
system:
"You are a helpful assistant that searches Reddit for relevant information.",
});
// Create the network
const redditSearchNetwork = createNetwork({
name: "reddit_search_network",
description: "A network that searches Reddit using Browserbase",
agents: [searchAgent],
maxIter: 2,
defaultModel: anthropic({
model: "claude-sonnet-4-6",
max_tokens: 4096,
}),
});
```
## 3. Create a Browserbase tool
Configure the Browserbase SDK and create a tool that can search Reddit:
```typescript theme={null}
import {
anthropic,
createAgent,
createNetwork,
createTool,
} from "@inngest/agent-kit";
import { z } from "zod";
import { chromium } from "playwright-core";
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY as string,
});
// Create a tool to search Reddit using Browserbase
const searchReddit = createTool({
name: "search_reddit",
description: "Search Reddit posts and comments",
parameters: z.object({
query: z.string().describe("The search query for Reddit"),
}),
handler: async ({ query }, { step }) => {
return await step?.run("search-on-reddit", async () => {
// Create a new session
const session = await bb.sessions.create();
// Connect to the session
const browser = await chromium.connectOverCDP(session.connectUrl);
try {
const page = await browser.newPage();
// Construct the search URL
const searchUrl = `https://search-new.pullpush.io/?type=submission&q=${query}`;
console.log(searchUrl);
await page.goto(searchUrl);
// Wait for results to load
await page.waitForSelector("div.results", { timeout: 10000 });
// Extract search results
const results = await page.evaluate(() => {
const posts = document.querySelectorAll("div.results div:has(h1)");
return Array.from(posts).map((post) => ({
title: post.querySelector("h1")?.textContent?.trim(),
content: post.querySelector("div")?.textContent?.trim(),
}));
});
console.log("results", JSON.stringify(results, null, 2));
return results.slice(0, 5); // Return top 5 results
} finally {
await browser.close();
}
});
},
});
```
Configure your `BROWSERBASE_API_KEY` in the `.env` file. You can find your API key in the Browserbase dashboard.
Build tools using Browserbase with Inngest's `step.run()` function. This ensures the tool only runs once across multiple runs.
See the [multi-step tools page](https://docs.agentkit.ai/tools/multi-step-tools) for more on `step.run()`.
## 4. Put it all together
Here's the full example:
```typescript theme={null}
// Add the tool to the agent
searchAgent.tools.push(searchReddit);
// Run the agent with a query
const executor = redditSearchNetwork.createExecutor();
// Execute the network with a query
const execution = await executor.execute({
input: "Find discussions about climate change solutions",
});
console.log(execution.output);
```
For a complete working example, check out the [Reddit search agent using Browserbase example](https://github.com/inngest/agentkit/tree/main/examples/reddit-search-agent-browserbase).
## Next steps
Full AgentKit SDK reference and examples
Autonomous browser automation SDK
Sessions, proxies, and browser configuration
# Add Stagehand to AgentKit tools
Source: https://docs.browserbase.com/integrations/inngest/stagehand
Use Stagehand, the SDK for browser agents, with AgentKit.
Stagehand comes with 4 primary APIs that can be directly used as tools:
* `goto()`: navigate to a specific URL
* `observe()`: observe the current page
* `extract()`: extract data from the current page
* `act()`: take action on the current page
These methods can be used as tools in AgentKit, enabling agents to browse the web autonomously.
Below is an example of a simple search agent that uses Stagehand to search the web:
```typescript theme={null}
import { createAgent, createTool } from "@inngest/agent-kit";
import { z } from "zod";
import { getStagehand, stringToZodSchema } from "./utils.js";
const webSearchAgent = createAgent({
name: "web_search_agent",
description: "I am a web search agent.",
system: `You are a web search agent.
`,
tools: [
createTool({
name: "navigate",
description: "Navigate to a given URL",
parameters: z.object({
url: z.string().describe("the URL to navigate to"),
}),
handler: async ({ url }, { step, network }) => {
return await step?.run("navigate", async () => {
const stagehand = await getStagehand(
network?.state.kv.get("browserbaseSessionID")!
);
const page = stagehand.context.pages()[0];
await page.goto(url);
return `Navigated to ${url}.`;
});
},
}),
createTool({
name: "extract",
description: "Extract data from the page",
parameters: z.object({
instruction: z
.string()
.describe("Instructions for what data to extract from the page"),
schema: z
.string()
.describe(
"A string representing the properties and types of data to extract, for example: '{ name: string, age: number }'"
),
}),
handler: async ({ instruction, schema }, { step, network }) => {
return await step?.run("extract", async () => {
const stagehand = await getStagehand(
network?.state.kv.get("browserbaseSessionID")!
);
const zodSchema = stringToZodSchema(schema);
return await stagehand.extract(instruction, zodSchema);
});
},
}),
createTool({
name: "act",
description: "Perform an action on the page",
parameters: z.object({
action: z
.string()
.describe("The action to perform (e.g. 'click the login button')"),
}),
handler: async ({ action }, { step, network }) => {
return await step?.run("act", async () => {
const stagehand = await getStagehand(
network?.state.kv.get("browserbaseSessionID")!
);
return await stagehand.act(action);
});
},
}),
createTool({
name: "observe",
description: "Observe the page",
parameters: z.object({
instruction: z
.string()
.describe("Specific instruction for what to observe on the page"),
}),
handler: async ({ instruction }, { step, network }) => {
return await step?.run("observe", async () => {
const stagehand = await getStagehand(
network?.state.kv.get("browserbaseSessionID")!
);
return await stagehand.observe(instruction);
});
},
}),
],
});
```
These 4 AgentKit tools using Stagehand enable the Web Search Agent to browse the web autonomously.
The `getStagehand()` helper function retrieves the persisted instance created for the network execution.
For a complete example of a simple search agent using Stagehand, check out the [Simple Search Agent using Stagehand example](https://github.com/inngest/agent-kit/tree/main/examples/simple-search-stagehand).
# LangChain Deep Agents with Browserbase
Source: https://docs.browserbase.com/integrations/langchain/deepagents
Build a Deep Agent that browses the web with Browserbase Search, Fetch, and Stagehand.
[LangChain Deep Agents](https://github.com/langchain-ai/deepagents) is a framework for building planner-style agents that delegate work to subagents and pause for human approval at sensitive steps. Pair it with Browserbase to give the agent cheap web context, rendered extraction, and full interactive browser sessions.
## Architecture
This integration exposes Browserbase to the Deep Agent as four Python tools:
* `browserbase_search`: fast discovery with the Browserbase Search API.
* `browserbase_fetch`: quick retrieval of static pages with the Browserbase Fetch API.
* `browserbase_rendered_extract`: Stagehand-backed extraction for JavaScript-heavy pages.
* `browserbase_interactive_task`: a Stagehand agent that handles clicks, typing, logins, and form submissions.
The first two run on the main agent for cheap context. The last two live on a `browser-specialist` subagent that isolates browser-heavy work from the planner. Stateful actions go through Deep Agents `interrupt_on`, so you approve each interactive task at the tool boundary.
The full source is on [GitHub](https://github.com/browserbase/integrations/tree/main/examples/integrations/langchain/deepagents-browserbase).
## Quickstart
Go to the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Copy your API key and set the `BROWSERBASE_API_KEY` environment variable.
```bash theme={null}
pip install browserbase stagehand deepagents langchain-openai beautifulsoup4 python-dotenv
```
The Deep Agent model client and the Browserbase tools both read from environment variables.
```bash theme={null}
export BROWSERBASE_API_KEY="bb_..."
# Optional overrides
export DEEPAGENT_MODEL="gpt-5.4"
export DEEPAGENT_BASE_URL="https://"
export STAGEHAND_MODEL="google/gemini-3-flash-preview"
export STAGEHAND_AGENT_MODEL="anthropic/claude-sonnet-4-6"
```
Point `DEEPAGENT_BASE_URL` at any OpenAI-compatible endpoint. With the [Browserbase Model Gateway](/platform/model-gateway/overview), you can run the entire sample with a single Browserbase API key.
Wrap the Browserbase SDK and Stagehand in `@tool`-decorated functions. The main agent gets `browserbase_search` and `browserbase_fetch`; the subagent gets the rendered and interactive variants.
```python theme={null}
from browserbase import Browserbase
from langchain.tools import tool
from stagehand import AsyncStagehand
@tool
def browserbase_search(query: str, num_results: int = 5) -> str:
"""Search the web with Browserbase. Use this first for discovery."""
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
response = bb.search.web(query=query, num_results=num_results)
# ... serialize results to JSON
@tool
def browserbase_fetch(url: str, use_proxy: bool = False) -> str:
"""Fetch page content without a browser session. Best for static pages."""
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
response = bb.fetch_api.create(url=url, proxies=use_proxy)
# ... extract title and text from HTML
```
See the [full `browser_tools.py`](https://github.com/browserbase/integrations/blob/main/examples/integrations/langchain/deepagents-browserbase/browser_tools.py) for the Stagehand-backed `browserbase_rendered_extract` and `browserbase_interactive_task` tools.
Give the planner the cheap tools, register the browser subagent, and gate `browserbase_interactive_task` behind `interrupt_on`.
```python theme={null}
from deepagents import create_deep_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
BROWSER_SUBAGENT = {
"name": "browser-specialist",
"description": "Handles JS-heavy browsing and interactive tasks via Browserbase.",
"system_prompt": "Use rendered extraction for read-only work. Use interactive tasks only for clicks, typing, login, or form submission.",
"tools": [browserbase_rendered_extract, browserbase_interactive_task],
}
agent = create_deep_agent(
model=ChatOpenAI(model="gpt-5.4", base_url=os.getenv("DEEPAGENT_BASE_URL")),
tools=[browserbase_search, browserbase_fetch],
subagents=[BROWSER_SUBAGENT],
system_prompt="Start with browserbase_search for discovery. Prefer browserbase_fetch for static reads. Delegate rendered or multi-step work to the browser-specialist.",
interrupt_on={
"browserbase_interactive_task": {
"allowed_decisions": ["approve", "edit", "reject"]
}
},
checkpointer=MemorySaver(),
)
```
When the agent calls `browserbase_interactive_task`, the run pauses. Approve, edit the arguments, or reject before the browser session executes.
```python theme={null}
from langgraph.types import Command
result = agent.invoke(
{"messages": [{"role": "user", "content": "Research the Browserbase Fetch API and cite the docs."}]},
config={"configurable": {"thread_id": "demo"}},
version="v2",
)
while result.interrupts:
# Show pending tool call to a human, collect decisions, then resume:
decisions = [{"type": "approve"}]
result = agent.invoke(
Command(resume={"decisions": decisions}),
config={"configurable": {"thread_id": "demo"}},
version="v2",
)
print(result.value["messages"][-1].content)
```
## When to use which tool
| Tool | Use when | Cost |
| ------------------------------ | -------------------------------------------------------------- | -------------------------------------------------- |
| `browserbase_search` | You need to discover URLs from a query. | Cheap, no browser session. |
| `browserbase_fetch` | You have a URL and the page renders without JavaScript. | Cheap, no browser session. |
| `browserbase_rendered_extract` | The page needs JavaScript to render content you want to read. | Full browser session. |
| `browserbase_interactive_task` | The task requires clicking, typing, login, or form submission. | Full browser session, gated behind `interrupt_on`. |
Front-load the cheap tools. Escalate to a browser session only when the page or task demands it.
## Why approve at the tool boundary
Deep Agents `interrupt_on` pauses the run when the agent picks a guarded tool. Putting human approval on `browserbase_interactive_task` means the human sees the exact `start_url` and `task` arguments before any clicks, typing, or form submission run in the browser. Browserbase records the resulting session, so you get a replay of every approved action.
## Further reading
The Deep Agents framework (planners, subagents, and human-in-the-loop interrupts).
Cheap, token-efficient web context for agents. Use these before opening a browser.
The SDK for browser agents. Powers rendered extraction and interactive tasks.
Frontier models behind a single Browserbase API key with unified billing.
# LangChain integration
Source: https://docs.browserbase.com/integrations/langchain/introduction
Add Browserbase to your LangChain application (Python).
[LangChain](https://www.langchain.com/) is a Python framework to build applications on top of large-language models (OpenAI, Llama, Gemini).
Building on top of LLMs comes with many challenges:
* Gathering and preparing the data (context) and providing memory to models
* Orchestrating tasks to match LLM API requirements (ex, rate limiting, chunking)
* Parse the different LLM result format
Langchain comes with a set of high-level concepts and tools to cope with those challenges:
* [Retrieval modules](https://js.langchain.com/v0.1/docs/modules/data_connection/retrievers/) such as [Document Loaders](https://js.langchain.com/docs/modules/data_connection/document_loaders/) or [Text splitter](https://js.langchain.com/docs/modules/data_connection/document_transformers/) help with gathering and preparing the data provided to the models
* [Model I/O](https://js.langchain.com/docs/modules/model_io/) is a set of tools that help to normalize the APIs across multiple models (*ex: Prompt Templates*)
* [Agents](https://js.langchain.com/docs/modules/agents/) and Tools help to build reasoning (*ex: how to answer based on provided context, what actions to take*)
* [Chains](https://js.langchain.com/docs/modules/chains/) help in orchestrating all the above
Browserbase provides a `Document Loader` to enable your LangChain application to browse the web to:
* Extract text or raw HTML, including from web pages using JavaScript or dynamically rendered text
* Load images via screenshots
## Add Browserbase to your LangChain application
Pick the guide that matches what you're building.
Add the Browserbase `Document Loader` to your LangChain Python application.
Build a Deep Agent that browses with Browserbase Search, Fetch, and Stagehand.
# Configure Browserbase for LangChain
Source: https://docs.browserbase.com/integrations/langchain/python
Go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input.
```bash theme={null}
# If you haven't installed pipx (MacOS)
brew install pipx
# Install Dependencies
pipx install browserbase playwright langchain_community --include-deps
```
**Load documents**
```python theme={null}
from langchain_community.document_loaders import BrowserbaseLoader
import os
from dotenv import load_dotenv
load_dotenv()
BROWSERBASE_API_KEY = os.getenv("BROWSERBASE_API_KEY")
loader = BrowserbaseLoader(
api_key=BROWSERBASE_API_KEY,
urls=[
# load multiple pages
"https://www.espn.com",
"https://lilianweng.github.io/posts/2023-06-23-agent/"
],
text_content=True,
)
documents = loader.load()
print(documents)
```
### Loader options
* `urls` Required. A list of URLs to fetch.
* `text_content` Retrieve only text content. Default is `False`.
* `api_key` Browserbase API key. Default is `BROWSERBASE_API_KEY` env variable.
* `session_id` Optional. Provide an existing Session ID.
* `proxy` Optional. Enable/Disable Proxies.
# Mastra integration
Source: https://docs.browserbase.com/integrations/mastra/introduction
Build browser agents with Mastra and Stagehand
Mastra is a TypeScript framework for building AI agents, applications, and workflows. It provides a unified interface for integrating various AI models, tools, and data sources into sophisticated automation systems.
The Mastra integration with Stagehand lets AI agents interact with web pages through Browserbase. You can build intelligent agents that navigate websites, extract data, and perform complex web interactions with natural language instructions.
Key capabilities include:
1. **Natural Language Web Automation**: Direct AI agents to perform web actions using simple instructions like "click the sign in button" or "extract all product prices"
2. **Intelligent Element Detection**: AI-powered element observation that can identify and locate page elements based on context and purpose
3. **Structured Data Extraction**: Extract specific information from web pages with custom schemas and validation
4. **Session Management**: Automatic browser session handling with smart reconnection and timeout management
## Core components
### Browser agent
An AI-powered agent using OpenAI's GPT-4 that provides a natural language interface to web automation. The agent can understand complex instructions and break them down into appropriate tool calls.
### Stagehand tools
Three specialized tools that provide comprehensive web automation capabilities:
* **`stagehandActTool`**: Performs actions on web pages (clicking, typing, navigation)
* **`stagehandObserveTool`**: Identifies and locates elements on web pages for planning actions
* **`stagehandExtractTool`**: Extracts structured data from web pages with custom schemas
### Session manager
Handles browser session initialization, automatic timeouts, error recovery, and reconnection logic to ensure reliable web automation.
## Use cases
The Mastra + Stagehand integration is perfect for:
* **Data Extraction**: Extract data from multiple pages with AI-guided navigation
* **Automated Testing**: Create intelligent test scenarios that adapt to UI changes
* **Lead Generation**: Gather contact information and company data from websites
* **Market Research**: Monitor competitor pricing, product listings, and content
* **Form Automation**: Fill out and submit forms across different websites
* **Content Management**: Update content across multiple platforms automatically
## Architecture flow
```
User Query → Mastra Agent → Stagehand Tools → Browser Session → Web Page → Extracted Data → Agent Response
```
The integration leverages Mastra's agent orchestration with Stagehand's browser automation to create a seamless web interaction experience powered by AI.
## Get started
Set up Mastra with Stagehand integration and build your first web automation agent.
# Mastra agent quickstart
Source: https://docs.browserbase.com/integrations/mastra/quickstart
Build AI-powered browser agents with Mastra and Stagehand
Go over the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Then copy your API Key directly from the input and set the `BROWSERBASE_API_KEY` environment variable.
Create an OpenAI account and get your API key from the [OpenAI platform](https://platform.openai.com/api-keys).
Set the environment variable:
```bash theme={null}
export OPENAI_API_KEY=your_openai_api_key
```
Initialize a new Node.js project:
```bash theme={null}
mkdir mastra-web-agent
cd mastra-web-agent
npm init -y
```
Install the required packages:
```bash theme={null}
npm install @mastra/core @mastra/memory @mastra/libsql mastra @ai-sdk/openai @browserbasehq/stagehand zod
npm install -D @types/node tsx typescript
```
Create `src/mastra/tools/index.ts` with the web automation tools:
```typescript src/mastra/tools/index.ts theme={null}
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
import { Stagehand } from "@browserbasehq/stagehand";
export const stagehandActTool = createTool({
id: 'web-act',
description: 'Take an action on a webpage using Stagehand',
inputSchema: z.object({
url: z.string().optional().describe('URL to navigate to (optional if already on a page)'),
action: z.string().describe('Action to perform (e.g., "click sign in button", "type hello in search field")'),
}),
outputSchema: z.object({
success: z.boolean(),
message: z.string(),
}),
execute: async ({ context }) => {
const stagehand = await sessionManager.ensureStagehand();
const page = stagehand.context.pages()[0];
try {
if (context.url) {
await page.goto(context.url);
}
if (context.action) {
await stagehand.act(context.action);
}
return {
success: true,
message: `Successfully performed: ${context.action}`
};
} catch (error: any) {
throw new Error(`Stagehand action failed: ${error.message}`);
}
},
});
export const stagehandObserveTool = createTool({
id: 'web-observe',
description: 'Observe elements on a webpage using Stagehand to plan actions',
inputSchema: z.object({
url: z.string().optional().describe('URL to navigate to (optional if already on a page)'),
instruction: z.string().describe('What to observe (e.g., "find the sign in button")'),
}),
outputSchema: z.array(z.any()).describe('Array of observable actions'),
execute: async ({ context }) => {
const stagehand = await sessionManager.ensureStagehand();
const page = stagehand.context.pages()[0];
try {
if (context.url) {
await page.goto(context.url);
}
return await stagehand.observe(context.instruction);
} catch (error: any) {
throw new Error(`Stagehand observation failed: ${error.message}`);
}
},
});
export const stagehandExtractTool = createTool({
id: 'web-extract',
description: 'Extract data from a webpage using Stagehand',
inputSchema: z.object({
url: z.string().optional().describe('URL to navigate to (optional if already on a page)'),
instruction: z.string().describe('What to extract (e.g., "extract all product prices")'),
schema: z.record(z.any()).optional().describe('Zod schema definition for data extraction'),
}),
outputSchema: z.any().describe('Extracted data according to schema'),
execute: async ({ context }) => {
const stagehand = await sessionManager.ensureStagehand();
const page = stagehand.context.pages()[0];
try {
if (context.url) {
await page.goto(context.url);
}
const defaultSchema = z.object({
content: z.string()
});
return await stagehand.extract(
context.instruction,
context.schema ? z.object(context.schema) : defaultSchema
);
} catch (error: any) {
throw new Error(`Stagehand extraction failed: ${error.message}`);
}
},
});
```
Create `src/mastra/agents/index.ts`:
```typescript src/mastra/agents/index.ts theme={null}
import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { stagehandActTool, stagehandObserveTool, stagehandExtractTool } from '../tools';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
const memory = new Memory({
storage: new LibSQLStore({
url: "file:./mastra.db",
}),
});
export const webAgent = new Agent({
name: 'Web Assistant',
instructions: `
You are a helpful web assistant that can navigate websites and extract information.
Use the stagehandActTool to perform actions on webpages.
Use the stagehandObserveTool to find elements on webpages.
Use the stagehandExtractTool to extract data from webpages.
`,
model: openai('gpt-4o'),
tools: { stagehandActTool, stagehandObserveTool, stagehandExtractTool },
memory: memory
});
```
Create `src/mastra/index.ts`:
```typescript src/mastra/index.ts theme={null}
import { Mastra } from '@mastra/core/mastra';
import { createLogger } from '@mastra/core/logger';
import { webAgent } from './agents';
export const mastra = new Mastra({
agents: { webAgent },
logger: createLogger({
name: 'Mastra',
level: 'info',
}),
});
```
Create a `.env` file in the project root:
```bash .env theme={null}
BROWSERBASE_API_KEY=your_browserbase_api_key
OPENAI_API_KEY=your_openai_api_key
```
Add to your `package.json`:
```json package.json theme={null}
{
"scripts": {
"dev": "mastra dev"
}
}
```
Run the development server:
```bash theme={null}
npm run dev
```
This starts the Mastra interface where you can interact with your web automation agent.
## How it works
The integration provides three main tools for your AI agent:
* **Act**: Perform actions like clicking buttons or filling forms
* **Observe**: Find and identify elements on web pages
* **Extract**: Pull structured data from websites
## Try these commands
Once your agent is running, try these natural language instructions:
```
"Go to example.com and find the contact button"
```
```
"Navigate to a news website and extract the main headlines"
```
```
"Search for 'AI automation' on Google"
```
```
"Extract product prices from an e-commerce site"
```
The agent will automatically choose the right tools and execute the web automation tasks.
Find the sample project for this integration
## Next steps
* **Custom instructions**: Modify the agent's behavior in `src/mastra/agents/index.ts`
* **Add tools**: Create additional tools for specific automation needs
* **Production setup**: Add proper error handling and logging for production use
You now have a fully functional AI agent that can automate web tasks through natural language! Learn more about advanced features in the [Mastra documentation](https://mastra.ai/docs).
# Browserbase MCP server
Source: https://docs.browserbase.com/integrations/mcp/introduction
Give MCP clients a browser through Model Context Protocol integration with Stagehand
The Browserbase MCP server gives any MCP client a browser. Built on [Stagehand](https://docs.stagehand.dev/), it lets agents interact with the web using natural language commands.
The hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
endpoint is served on Browserbase infrastructure.
You can also run the MCP server locally with STDIO, but the
hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
endpoint is recommended for most users.
## Key features
Control browsers using plain English commands like "click the login button" or "fill out the contact form"
Navigate, click, and fill forms with ease
Extract structured data from any website automatically
Create, reuse, and close browser sessions with explicit MCP tools
## Core benefits
No need to learn complex selectors or automation syntax. Simply describe what you want to do in natural language.
Get started in minutes with either hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) or local STDIO.
Stagehand's AI understands web page context and can adapt to different layouts and designs.
Navigate, click, type, scroll, and interact with any web element.
Extract structured information from complex web pages automatically.
Maintain authentication states and cookies across multiple interactions.
Hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) runs on Browserbase infrastructure for consistent performance.
Handle multiple concurrent sessions and high-volume automation tasks.
Verified, proxy support, and browser identity capabilities.
Detailed session recordings and debugging information.
## Use cases
Track product prices, availability, and competitor information
Gather data from multiple sources for analysis and reporting
Collect articles, posts, and media from various websites
Extract contact information and business data from directories
Create comprehensive test suites for web applications
Test functionality across different browser environments
Simulate real user interactions and workflows
Track page load times and user experience metrics
Automatically fill and submit complex web forms
Extract data and generate automated reports
Schedule posts and monitor engagement across platforms
Automate repetitive web-based business processes
## Getting started
Choose hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) (recommended) or local STDIO based on your needs.
Set up your Browserbase API credentials in MCP configuration. Get API keys
from the [Browserbase Dashboard](https://www.browserbase.com/overview).
Begin using natural language commands to control browsers through your MCP client.
Ready to get started? Check out the [Setup Guide](/integrations/mcp/setup).
## Further reading
Get started with installation and configuration
Learn more about the MCP protocol
Explore Browserbase features and capabilities
# Browserbase MCP server setup
Source: https://docs.browserbase.com/integrations/mcp/setup
Add the Browserbase MCP Server to your MCP client
## Quick installation
One-click installation directly in Cursor
You can also add Browserbase MCP to Claude Code with a single command:
```bash theme={null}
claude mcp add --transport http browserbase "https://mcp.browserbase.com/mcp?browserbaseApiKey=YOUR_BROWSERBASE_API_KEY"
```
Browserbase supports both local STDIO and hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) (SHTTP). Hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) is recommended for most users.
## Endpoint
Hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) endpoint (served on Browserbase infrastructure):
```text theme={null}
https://mcp.browserbase.com/mcp
```
## Prerequisites
Get your Browserbase API key from the [Browserbase Dashboard](https://www.browserbase.com/overview).
Then copy your API Key directly from the input.
## Query parameters (hosted [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http))
### Required for tool calls
Browserbase API key.
### Optional
| Query Param | Type | Behavior |
| ------------- | -------------- | ------------------------------------------ |
| `modelName` | string | Defaults to `google/gemini-2.5-flash-lite` |
| `modelApiKey` | string | Required when `modelName` is non-default |
| `keepAlive` | boolean string | `"true"` or `"false"` |
| `proxies` | boolean string | `"true"` or `"false"` |
| `verified` | boolean string | `"true"` or `"false"` |
Boolean query values must be exact strings: `"true"` or `"false"`.
## Available tools
Every tool below accepts an optional `sessionId` parameter. Pass the `sessionId` returned by `start` to target or reattach to that specific Browserbase session; if omitted, the tool uses the transport's current active session. This matters for MCP clients (e.g. ChatGPT) that don't carry the same `Mcp-Session-Id` across requests: pass `sessionId` explicitly on every call to reliably target the right browser session. See [Session reattachment](#session-reattachment) below.
Navigate to any URL in the browser
The URL to navigate to
Optional Browserbase session ID to target or reattach to. Defaults to the current active session.
Perform an action on the web page using natural language
The action to perform (e.g., "click the login button", "fill form field")
Optional Browserbase session ID to target or reattach to. Defaults to the current active session.
Observe and find actionable elements on the page.
Specific instruction for observation (e.g., "find the login button", "locate search form")
Optional Browserbase session ID to target or reattach to. Defaults to the current active session.
Extract data from the current page.
Optional extraction instruction.
Optional Browserbase session ID to target or reattach to. Defaults to the current active session.
Create or reuse a Browserbase session and set it as active for the current MCP transport session.
**Inputs**
Optional existing Browserbase session ID to reuse instead of creating a new session.
**Output**
Browserbase session ID.
Close a Browserbase session.
Optional Browserbase session ID to close. Defaults to the current active session. Closing a session by explicit ID only clears the transport's active-session state when that ID is the current session.
### Session reattachment
The hosted endpoint (`mcp.browserbase.com`) keeps track of an active Browserbase session per MCP transport (`Mcp-Session-Id`). Some MCP clients open a new transport on every tool call instead of reusing one, which means the server has no active session to fall back to and calls fail with `No active session`.
To work reliably with these clients, pass the `sessionId` returned by `start` on every subsequent tool call. An explicit `sessionId` always takes priority over the transport's current session. The hosted endpoint adopts a session as the transport's active session only after confirming that session still exists, so a stale or incorrect `sessionId` surfaces an error instead of silently redirecting a later call.
## Local command-line flags
Command-line flags are only available when running the server locally (`npx @browserbasehq/mcp` with flags or local development setup).
| Flag | Description |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--proxies` | Enable Browserbase proxies for the session |
| `--verified` | Enable Browserbase Verified (Scale Plan only) |
| `--keepAlive` | Enable Browserbase Keep Alive Session |
| `--contextId ` | Specify a Browserbase Context ID to use |
| `--persist [boolean]` | Whether to persist the Browserbase context (default: true) |
| `--port ` | Port to listen on for HTTP or [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) transport |
| `--host ` | Host to bind server to (default: localhost, use 0.0.0.0 for all interfaces) |
| `--browserWidth ` | Browser viewport width (default: 1024) |
| `--browserHeight ` | Browser viewport height (default: 768) |
| `--modelName ` | The model to use for Stagehand (default: google/gemini-2.5-flash-lite) |
| `--modelApiKey ` | API key for the custom model provider (required when using custom models) |
| `--experimental` | Enable experimental features (default: false) |
## Installation methods
Use your MCP client config:
```json theme={null}
{
"mcpServers": {
"browserbase": {
"url": "https://mcp.browserbase.com/mcp?browserbaseApiKey=YOUR_BROWSERBASE_API_KEY"
}
}
}
```
For custom models, include `modelName` and `modelApiKey`:
```json theme={null}
{
"mcpServers": {
"browserbase": {
"url": "https://mcp.browserbase.com/mcp?browserbaseApiKey=YOUR_BROWSERBASE_API_KEY&modelName=openai/gpt-4.1&modelApiKey=YOUR_MODEL_API_KEY"
}
}
}
```
The easiest way to get started locally is using the NPM package.
If you'd like to use a different model, pass the model name and keys in the args. More info [here](#local-command-line-flags).
Go into your MCP Config JSON and add the Browserbase Server:
```json Claude Desktop theme={null}
{
"mcpServers": {
"browserbase": {
"command": "npx",
"args": ["@browserbasehq/mcp"],
"env": {
"BROWSERBASE_API_KEY": "your_api_key",
"GEMINI_API_KEY": "your_gemini_api_key"
}
}
}
}
```
That's it! Reload your MCP client and you'll be able to use Browserbase.
For local development or customization, you can run the server locally.
```bash theme={null}
# Clone the Repo
git clone https://github.com/browserbase/mcp-server-browserbase.git
cd mcp-server-browserbase
# Install the dependencies and build the project
npm install && npm run build
```
You can run locally using either STDIO or [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http).
Add the following to your MCP Config JSON file:
```json theme={null}
{
"mcpServers": {
"browserbase": {
"command": "node",
"args": ["/path/to/mcp-server-browserbase/cli.js"],
"env": {
"BROWSERBASE_API_KEY": "your_api_key",
"GEMINI_API_KEY": "your_gemini_api_key"
}
}
}
}
```
First, run the server:
```bash theme={null}
node cli.js --port 8931
```
Then add this to your MCP Config JSON file:
```json theme={null}
{
"mcpServers": {
"browserbase": {
"url": "http://localhost:8931/mcp",
"env": {
"BROWSERBASE_API_KEY": "your_api_key",
"GEMINI_API_KEY": "your_gemini_api_key"
}
}
}
}
```
Reload your MCP client and you should be good to go!
## Verify installation
Restart/refresh your MCP client app and verify tools are available.
Get started by asking your MCP client to navigate to any page and see your Browserbase browser in action on the [dashboard](https://www.browserbase.com/sessions).
Try: "Navigate to example.com and extract the main heading"
## Further reading
Learn more about the MCP protocol
Explore Browserbase features and capabilities
Get help from the Browserbase support team
# Introduction
Source: https://docs.browserbase.com/integrations/mongo-db/introduction
Intelligent data extraction with MongoDB storage using Stagehand
The **MongoDB integration** combines the power of [Stagehand's](https://docs.stagehand.dev/) AI-driven data extraction capabilities with MongoDB's flexible document storage to create a comprehensive data extraction and analysis pipeline.
This integration demonstrates how to extract structured product data from e-commerce websites intelligently and store it in MongoDB for persistent querying and analysis.
Uses natural language instructions to extract structured data from complex web pages
Built-in Zod schemas ensure data consistency and type safety
Persistent storage with automatic indexing and optimized queries
Built-in analytics queries for immediate insights into extracted data
## What you'll learn
By following this integration guide, you'll learn how to:
* Set up intelligent data extraction with natural language instructions
* Design robust data schemas for extracted web content
* Implement MongoDB storage with automatic indexing
* Build data analysis pipelines for extracted data
* Handle errors and edge cases in data extraction workflows
* Optimize performance for large-scale data extraction
This integration is perfect for developers who want to combine the power of AI-driven data extraction with robust data storage and analysis capabilities.
## Next steps
Get up and running with the MongoDB integration
Explore the complete API documentation
# Quickstart
Source: https://docs.browserbase.com/integrations/mongo-db/quickstart
Build an AI-powered data extraction agent that pulls product data from e-commerce sites and stores it in MongoDB with automatic schema validation and data analysis capabilities.
An AI-powered data extraction agent that pulls product data from e-commerce sites and stores it in MongoDB with automatic schema validation and data analysis.
## Before you start
Ensure you have these requirements ready:
Required runtime environment
Local install or MongoDB Atlas
Browserbase API key
## Step 1: Project setup
### Clone and install
```bash theme={null}
# Clone the integration template
npx degit browserbase/integrations/examples/integrations/mongodb/typescript browserbase-mongodb
cd browserbase-mongodb
# Install all dependencies
npm install
# Install browser binaries
npx playwright install
```
**Core packages:**
* `@browserbasehq/stagehand` - AI-powered data extraction
* `mongodb` - MongoDB driver for data storage
* `zod` - Schema validation for type safety
**Utilities:**
* `chalk` & `boxen` - Terminal styling and output formatting
* `playwright` - Browser automation engine
## Step 2: Start MongoDB
**Required**: MongoDB must be running on your system before proceeding. Start it with `mongod` if installed locally, or ensure your MongoDB Atlas connection is ready.
If using local MongoDB, ensure it's running:
```bash theme={null}
# Start MongoDB (if installed locally)
mongod
```
**MongoDB Atlas users** can skip this step as the database is already hosted in the cloud.
## Step 3: Configuration
### Environment variables
Create your `.env` file with the required configuration:
```bash Local MongoDB theme={null}
# Browserbase Configuration (Recommended)
BROWSERBASE_API_KEY=your_browserbase_api_key
# MongoDB Configuration
MONGO_URI=mongodb://localhost:27017
DB_NAME=scraper_db
# Stagehand Configuration
ANTHROPIC_API_KEY=your_anthropic_api_key_here
```
```bash MongoDB Atlas theme={null}
# Browserbase Configuration (Recommended)
BROWSERBASE_API_KEY=your_browserbase_api_key
# MongoDB Atlas Configuration
MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/
DB_NAME=scraper_db
# Stagehand Configuration
ANTHROPIC_API_KEY=your_anthropic_api_key_here
```
## Step 4: Configure Stagehand
The integration uses Browserbase cloud browsers:
```typescript stagehand.config.ts theme={null}
const StagehandConfig: V3Options = {
verbose: 1,
domSettleTimeout: 30_000,
// LLM Configuration
model: "anthropic/claude-sonnet-4-6",
// Run in Browserbase cloud (Recommended)
env: "BROWSERBASE",
apiKey: process.env.BROWSERBASE_API_KEY,
browserbaseSessionCreateParams: {
browserSettings: {
blockAds: true,
viewport: { width: 1024, height: 768 },
},
},
};
```
## Step 5: Run your first extraction
**What happens when you run the agent:**
1. Connects to MongoDB and creates necessary collections
2. Navigates to Amazon laptop category
3. Extracts product listings with AI-powered data extraction
4. Extracts detailed information for the first 3 products
5. Stores all data in MongoDB with schema validation
6. Runs analysis queries and displays results
### Execute the extraction
```bash theme={null}
npm start
```
## Customization options
### Extend data schema
Add custom fields to capture more product information:
```typescript theme={null}
const ProductSchema = z.object({
// Existing fields...
title: z.string(),
price: z.string().optional(),
rating: z.string().optional(),
// Add your custom fields
brand: z.string().optional(),
availability: z.string().optional(),
shippingInfo: z.string().optional(),
specifications: z.array(z.string()).optional(),
customerReviews: z.number().optional(),
});
```
### Custom extraction instructions
Modify the AI extraction to capture specific data:
```typescript theme={null}
const data = await stagehand.extract(
`Extract comprehensive product information including:
- Brand and model details
- Detailed specifications
- Availability and shipping information
- Customer ratings and review counts`,
ProductSchema,
);
```
## What's next?
Now that you have a working MongoDB + Stagehand integration:
Learn how to scale your data extraction across multiple sites and handle larger datasets.
Deploy your extraction pipeline to production with Browserbase.
***
**Need help?** Join the [Stagehand Slack community](https://stagehand.dev/slack) for support and to share your projects!
# n8n integration
Source: https://docs.browserbase.com/integrations/n8n/introduction
Build browser agents in n8n with the Browserbase node.
[n8n](https://n8n.io/) is a fair-code licensed workflow platform that connects hundreds of services through a visual, node-based editor. The Browserbase node brings browser agents, [Search](/platform/search/overview), and [Fetch](/platform/fetch/overview) into n8n so your workflows can gather context, inspect pages, and act on the web.
Use the `Agent` resource to run [Stagehand](https://stagehand.dev)-powered browser agents in cloud-hosted Browserbase sessions. Use [`Search`](/platform/search/overview) and [`Fetch`](/platform/fetch/overview) to collect web context before launching a browser session, or to handle lightweight retrieval tasks on their own. Results flow into the rest of your n8n workflow.
## Key features
Provide a URL and an instruction in plain English. Browser agents handle navigation, clicking, typing, and extraction from a visual workflow builder.
Choose between CUA (vision-based), DOM (selector-based), or Hybrid mode depending on your use case and performance needs.
Find relevant URLs with [Search](/platform/search/overview) or retrieve page content with [Fetch](/platform/fetch/overview) without creating a browser session.
Use Verified browsers, residential proxies, CAPTCHA solving, ad blocking, session recording, and multi-region support.
Pass sensitive data like passwords and API keys with `%variable%` placeholders. The LLM never sees the values.
Chain browser agent results with n8n's extensive node library. Send extracted data to Slack, store it in Google Sheets, trigger webhooks, and more.
## Agent modes
The `Agent` resource supports three modes that determine how the browser agent interacts with web pages:
| Mode | Description | Best for |
| ---------- | ------------------------------------------------------ | ------------------------------------------------- |
| **CUA** | Computer Use Agent, uses vision and screen coordinates | Complex UIs, visual interactions, dynamic content |
| **DOM** | Uses DOM selectors, works with any LLM | Speed, simple pages, structured content |
| **Hybrid** | Combines vision and DOM approaches | Fallback reliability, mixed content types |
## Model configuration
The `Agent` resource requires two model selections:
* **Driver Model**: Powers the browser session (navigation, DOM interactions)
* **Agent Model**: Orchestrates the high-level browser agent task
You can use [Model Gateway](/platform/model-gateway/overview) with only your Browserbase API key instead of adding a separate model provider key. Model Gateway lets you switch between supported models from the node. Browserbase bills model usage at market rate through your Browserbase account.
If you bring your own **Model API Key**, both the Driver Model and Agent Model must be from the **same provider**. For example, if you select a Gemini driver model, your agent model must also be a Google model, and your Model API Key must be a Google API key.
Supported providers: **Google** (Gemini), **OpenAI** (GPT-4o, Computer Use Preview), and **Anthropic** (Claude).
## What you can build
* **Research pipelines**: [Search](/platform/search/overview) the web, [Fetch](/platform/fetch/overview) candidate pages, and send promising targets to a browser agent
* **Data extraction flows**: Extract product prices, news headlines, or job listings, and pipe them into databases or spreadsheets
* **Form completion agents**: Fill out and submit forms across multiple sites as part of a larger workflow
* **Competitive intelligence**: Track competitor pricing, features, or content changes on a schedule
* **Lead enrichment**: Visit company websites to gather contact info, tech stacks, or social profiles
* **Web flow validation**: Validate web flows and capture results alongside your CI/CD pipeline
## Get started
Install the community node, configure credentials, and run your first browser agent workflow.
# n8n quickstart
Source: https://docs.browserbase.com/integrations/n8n/quickstart
Install the Browserbase community node, configure credentials, and use Agent, Search, and Fetch resources in n8n.
Go to the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Copy your **API Key**. If you still rely on a **Project ID** for older workflows, keep that available as well.
Don't have an account yet? [Sign up at browserbase.com](https://www.browserbase.com).
For browser agents, you can use [Browserbase Model Gateway](/platform/model-gateway/overview) with only your Browserbase API key. Model Gateway lets you switch between supported models in the node. Browserbase bills model usage at market rate through your Browserbase account.
You can also bring your own API key from the model provider whose models you'll use. Choose one:
* **Google**: [Google AI Studio](https://aistudio.google.com/apikey)
* **OpenAI**: [OpenAI Platform](https://platform.openai.com/api-keys)
* **Anthropic**: [Anthropic Console](https://console.anthropic.com/)
If you bring your own Model API Key, it must match the provider of **both** your Driver Model and Agent Model. For example, if you select `google/gemini-2.5-flash` as your driver and `google/gemini-2.5-computer-use-preview-10-2025` as your agent model, you need a **Google** API key.
In your n8n instance:
1. Go to **Settings** > **Community Nodes**
2. Click **Install a community node**
3. Enter `n8n-nodes-browserbase`
4. Click **Install**
Requires **n8n v1.60.0** or later. If you're self-hosting, make sure your instance is up to date.
1. Go to **Credentials** > **Add Credential**
2. Search for **"Browserbase"** and select **Browserbase API**
3. Fill in the three fields:
| Field | Value |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Browserbase API Key** | Your API key from Step 1 |
| **Browserbase Project ID** | Optional legacy project ID from Step 1 |
| **Model API Key** | Optional. Only required when using the `Agent` resource with your own model provider key instead of [Model Gateway](/platform/model-gateway/overview) |
4. Click **Save**
All Browserbase nodes in your workflows use these credentials. You only need to set them up once.
1. Create a new workflow (or open an existing one)
2. Click **+** to add a node and search for **"Browserbase"**
3. Select the **Resource** you want to use:
| Resource | Use case |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| **Agent** | Run a Stagehand-powered browser agent in a Browserbase session |
| **Search** | Find relevant URLs with the [Search API](/platform/search/overview) without creating a browser session |
| **Fetch** | Retrieve page content with the [Fetch API](/platform/fetch/overview) without creating a browser session |
To run a browser agent, choose **Resource = Agent** and configure the required fields:
| Field | Description |
| ---------------- | --------------------------------------------------------------------------------------- |
| **Resource** | `Agent` |
| **Starting URL** | The page where the agent begins (e.g., `https://news.ycombinator.com`) |
| **Instruction** | Natural language task (e.g., "Find the top 3 stories and return their titles and URLs") |
| **Driver Model** | Powers navigation and DOM interactions. Default: `google/gemini-2.5-flash` |
| **Mode** | `CUA` (vision), `DOM` (selectors), or `Hybrid` (both) |
| **Agent Model** | Orchestration model. Available options change based on selected mode |
When using your own Model API Key, the Driver Model and Agent Model must be from the **same provider**. With [Model Gateway](/platform/model-gateway/overview), you only need your Browserbase API key.
The same node also supports lightweight Browserbase APIs:
**[Search](/platform/search/overview)**
| Field | Description |
| --------------------- | ------------------------------------ |
| **Resource** | `Search` |
| **Query** | Search query to execute |
| **Number of Results** | Optional. How many results to return |
**[Fetch](/platform/fetch/overview)**
| Field | Description |
| ---------------------- | --------------------------------------------------- |
| **Resource** | `Fetch` |
| **URL** | URL to fetch |
| **Follow Redirects** | Optional. Follow HTTP redirects |
| **Allow Insecure SSL** | Optional. Allow invalid SSL certificates |
| **Use Proxies** | Optional. Route traffic through Browserbase proxies |
[`Search`](/platform/search/overview) and [`Fetch`](/platform/fetch/overview) only require your Browserbase API key. They do not create a browser session.
Click **Test Workflow** to execute. When `Resource = Agent`, the node returns an `AgentResult` object:
```json theme={null}
{
"success": true,
"message": "Task completed successfully",
"actions": [
{ "type": "act", "action": "clicked submit button" }
],
"completed": true,
"usage": {
"input_tokens": 1250,
"output_tokens": 340,
"inference_time_ms": 2500
},
"sessionId": "abc-123"
}
```
When `Resource = Search` or `Fetch`, the node returns the corresponding [Search](/platform/search/overview) or [Fetch](/platform/fetch/overview) API response, which you can pass to downstream n8n nodes.
You can connect additional nodes to process any of these outputs. Send them to a database, spreadsheet, Slack channel, or any other n8n integration.
## Optional settings
### Browser options
| Option | Type | Default | Description |
| --------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| Verified | boolean | `false` | Enable Verified browser sessions |
| Block Ads | boolean | `true` | Block ads during browsing |
| Record Session | boolean | `true` | Record the browser session for replay in the [Browserbase Dashboard](https://www.browserbase.com/sessions) |
| Solve Captchas | boolean | `false` | Automatically solve captchas encountered during execution |
| Viewport Width | number | `1288` | Browser viewport width in pixels |
| Viewport Height | number | `711` | Browser viewport height in pixels |
### Session options
| Option | Type | Default | Description |
| ----------- | ------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Region | select | `us-west-2` | Region where the browser runs. Options: `us-west-2` (Oregon), `us-east-1` (Virginia), `eu-central-1` (Frankfurt), and `ap-southeast-1` (Singapore) |
| Timeout | number | `300` | Session timeout in seconds |
| Use Proxies | boolean | `true` | Route traffic through residential proxies |
### Agent options
| Option | Type | Default | Description |
| ---------------- | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Max Steps | number | `20` | Maximum number of steps the agent can take |
| System Prompt | string | | Custom system prompt to guide the agent's behavior |
| Highlight Cursor | boolean | `true` | Highlight cursor during execution (CUA/Hybrid modes only) |
| Variables | collection | | Pass sensitive data to the agent using `%name%` placeholders in your instruction. The LLM sees placeholder names and optional descriptions but never the actual values. [Learn more](https://docs.stagehand.dev/v3/basics/agent#variables). (DOM and Hybrid modes) |
## Example workflows
**Agent: data extraction**
* Starting URL: `https://news.ycombinator.com`
* Instruction: `Find the top 3 stories and return their titles and URLs`
**Agent: form filling (with variables)**
* Starting URL: `https://example.com/contact`
* Instruction: `Fill out the contact form with name %name% and email %email%, then submit`
* Variables: `name = John Doe`, `email = john@example.com`
**Agent: navigation + action**
* Starting URL: `https://github.com`
* Instruction: `Search for "stagehand" and click on the first repository result`
**[Search](/platform/search/overview): web discovery**
* Query: `browserbase documentation`
* Number of Results: `5`
**[Fetch](/platform/fetch/overview): lightweight retrieval**
* URL: `https://www.browserbase.com`
* Follow Redirects: `true`
The Browserbase node has `usableAsTool` enabled for browser agents. You can use it as a tool from n8n's built-in AI Agent node so higher-level agents can decide when to send work to a browser agent.
Browse the source code, report issues, or contribute to the n8n community node.
# OpenAI Computer Use Agent
Source: https://docs.browserbase.com/integrations/openai-cua/introduction
Integrate OpenAI CUA with Browserbase for scalable browser agents
This guide walks you through integrating OpenAI's Computer Use Agent (CUA) with Browserbase for scalable cloud browser agents.
CUA is an AI model that can see the screen, understand context, and take actions within a browser, enabling advanced interaction with web applications. Pair CUA with Browserbase's scalable remote browser infrastructure to run AI-powered browser agents effortlessly in the cloud.
Try out the Computer Use Agent now: [cua.browserbase.com](https://cua.browserbase.com/)
## Prerequisites
* OpenAI API key with Computer Use Agent access
* Browserbase account and API key
* Python 3.8+
## Basic integration
This basic setup will get you up and running with a CUA agent using Browserbase as the underlying browser automation platform.
```bash theme={null}
git clone https://github.com/openai/openai-cua-sample-app.git
```
```bash theme={null}
pip install -r "requirements.txt"
```
```bash theme={null}
BROWSERBASE_API_KEY=YOUR_API_KEY
OPENAI_API_KEY=YOUR_OPENAI_API_KEY
OPENAI_ORG=YOUR_OPENAI_ORG
```
Update the prompt in your cli to change the behavior of the agent
```bash theme={null}
python cli.py --computer browserbase --input "go to hackernews, tell me the top news"
```
## Customizing the CUA agent
You can customize the CUA agent by updating the flags in the CLI:
* `--input`: The initial input to the agent (optional: the CLI will prompt you for input if not provided)
* `--debug`: Enable debug mode.
* `--show`: Show images (screenshots) during the execution.
* `--start-url`: Start the browsing session with a specific URL (only for browser environments). By default, the CLI will start the browsing session with `https://bing.com`.
## Related resources
Explore the full Browserbase documentation
Learn more about OpenAI's APIs
Find the sample project for this integration
# OpenClaw integration
Source: https://docs.browserbase.com/integrations/openclaw/introduction
Run OpenClaw's browser on Browserbase instead of OpenClaw's built-in local Chromium.
[OpenClaw](https://docs.openclaw.ai/) is an agent runtime with built-in tools, workspace automation, and browser control. For Browserbase users, the recommended OpenClaw integration path is the built-in browser plugin with a Browserbase-backed browser profile.
See [Remote browser vs local browser](/platform/browser/getting-started/remote-browser-versus-local-browser) for why Browserbase's managed browser beats self-hosted Chromium.
If you want to stay inside OpenClaw's native browser experience, start there first. If you want access to additional Browserbase platform features and more granular control over browser sessions, use the CLI path second.
## Recommended order
Recommended. Use OpenClaw's first-class browser integration with a Browserbase remote profile.
Use this when you want additional Browserbase platform features and more granular control over sessions, contexts, logs, fetch, search, and browser configuration.
## Common use cases
* Run OpenClaw browsing tasks on Browserbase instead of a local Chromium install
* Give OpenClaw access to Browserbase sessions with residential proxies, CAPTCHA handling, and session observability
* Keep local OpenClaw orchestration while moving browser execution to Browserbase infrastructure
# OpenClaw setup
Source: https://docs.browserbase.com/integrations/openclaw/setup
Run OpenClaw's browser on Browserbase instead of OpenClaw's built-in local Chromium.
This guide recommends two Browserbase setup paths for OpenClaw, in this order:
1. Use OpenClaw's built-in browser plugin with a Browserbase profile.
2. Use the [Browse CLI](/integrations/skills/browserbase-cli) if you specifically want `browse` workflows.
## Recommended: built-in browser plugin
This is the preferred path because it keeps you inside OpenClaw's first-class browser workflow while moving execution onto Browserbase infrastructure.
Create a Browserbase account, then copy your API key from the [Overview dashboard](https://www.browserbase.com/overview).
Add a Browserbase-backed profile in `~/.openclaw/openclaw.json`:
```json theme={null}
{
"browser": {
"enabled": true,
"defaultProfile": "browserbase",
"remoteCdpTimeoutMs": 3000,
"remoteCdpHandshakeTimeoutMs": 5000,
"profiles": {
"browserbase": {
"cdpUrl": "wss://connect.browserbase.com?apiKey=",
"color": "#F97316"
}
}
}
}
```
Restart OpenClaw so the browser service picks up the new Browserbase profile.
Test the profile with OpenClaw's native browser commands:
```bash theme={null}
openclaw browser --browser-profile browserbase status
openclaw browser --browser-profile browserbase open https://example.com
openclaw browser --browser-profile browserbase snapshot
```
Browserbase creates a browser session automatically when OpenClaw connects over WebSocket, so there is no separate manual session-creation step in this flow.
## Alternative: Browse CLI
If your workflow is centered on `browse` commands instead of OpenClaw's native browser commands, use the Browse CLI.
```bash theme={null}
npm i -g browse
```
```bash theme={null}
export BROWSERBASE_API_KEY="your_api_key"
```
Example commands:
```bash theme={null}
browse projects list
browse sessions get
browse fetch https://example.com
browse search "browser automation" --num-results 5
```
For the full CLI workflow reference, see the [Browse CLI guide](/integrations/skills/browserbase-cli).
# Evaluate browser agents with Prime Intellect
Source: https://docs.browserbase.com/integrations/prime-intellect/evals
Run browser agent evaluations using BrowserEnv and the Prime CLI.
Use `BrowserEnv` with the Prime CLI to evaluate browser agents on structured tasks. Each evaluation run spins up Browserbase sessions, feeds observations to your model, and collects reward signals, giving you reproducible benchmarks for browser-capable models.
## Prerequisites
API key from your Browserbase dashboard
Install via `uv add prime`
Install with browser extras: `uv add verifiers[browser]`
## Install and configure
### Set Browserbase credentials
Export your Browserbase credentials so `BrowserEnv` can create sessions:
```bash theme={null}
export BROWSERBASE_API_KEY=your_browserbase_api_key
```
### Install the Prime CLI
```bash theme={null}
uv add prime
prime login
```
### Install verifiers with browser support
```bash theme={null}
uv add verifiers[browser]
```
## Choose a BrowserEnv mode
`BrowserEnv` supports two observation/action modes. The mode is selected when you run an evaluation, either through the environment's default or via `-a` args.
### DOM mode (recommended)
The agent receives structured DOM content and issues natural language instructions via [Stagehand](https://docs.stagehand.dev/) tools (`navigate`, `observe`, `act`, `extract`). This is the default and works well for most browser tasks.
```bash theme={null}
prime eval run browser-dom-example -m openai/gpt-4.1 -k PRIME_API_KEY
```
### CUA mode
The agent receives screenshots and uses coordinate-based tool calls (`click`, `type_text`, `scroll`, `screenshot`). Use this for vision models trained on screenshot-grounded interaction.
```bash theme={null}
prime eval run browser-cua-example -m anthropic/claude-opus-4.5 -k PRIME_API_KEY
```
CUA mode deploys a sandbox server by default to handle connection to Browserbase's custom CDP driver, [Understudy](https://github.com/browserbase/stagehand/tree/main/packages/core/lib/v3/understudy), which overcomes performance limitations of Playwright. You can also run against a local server with `-a '{"use_sandbox": false}'`. See [Operational Notes](#operational-notes) below.
## Run an evaluation
### Install a hub environment
Install a published Browserbase environment from the Prime hub:
```bash theme={null}
prime env install browser-dom-example
```
### Run with default settings
```bash theme={null}
prime eval run browser-dom-example -m openai/gpt-4.1 -k PRIME_API_KEY
```
### Override evaluation parameters
Control the number of examples, rollouts, and environment-specific args:
```bash theme={null}
prime eval run browser-dom-example \
-m openai/gpt-4.1 \
-k PRIME_API_KEY \
-n 10 \
-r 2
```
| Flag | Short | Description |
| ------------------------ | ----- | ---------------------------------------------------------------------- |
| `--model` | `-m` | Model to evaluate (e.g. `openai/gpt-4.1`, `anthropic/claude-opus-4.5`) |
| `--api-key-var` | `-k` | Environment variable name for the model API key |
| `--num-examples` | `-n` | Number of task examples to evaluate |
| `--rollouts-per-example` | `-r` | Rollouts per example |
| `--env-args` | `-a` | JSON args passed to the environment's `load_environment()` |
| `--max-concurrent` | `-c` | Max concurrent requests |
| `--save-results` | `-s` | Save results to disk |
### Pass environment args
Use `-a` to pass JSON arguments to the environment. These are forwarded to the `load_environment()` function:
```bash theme={null}
# DOM mode with custom Stagehand model and max turns
prime eval run browser-dom-example \
-m openai/gpt-4.1 \
-k PRIME_API_KEY \
-a '{"max_turns": 20, "stagehand_model": "openai/gpt-4.1"}'
# CUA mode with proxies and Verified
prime eval run browser-cua-example \
-m anthropic/claude-opus-4.5 \
-k PRIME_API_KEY \
-a '{"proxies": true, "verified": true}'
```
### Run a published benchmark
Browserbase publishes browser benchmarks on the Prime hub:
```bash theme={null}
# Mind2Web benchmark
prime eval run browserbase/mind2web \
-m anthropic/claude-opus-4.5 \
-r 1 -n 10 \
-a '{"max_turns": 50, "proxies": true, "verified": true}'
# WebVoyager benchmark
prime eval run browserbase/webvoyager \
-m anthropic/claude-opus-4.5 \
-r 1 -n 4 \
-a '{"max_turns": 5, "proxies": true, "verified": true}'
```
### Run from a local environment
If your environment lives in a local directory:
```bash theme={null}
prime eval run ./my_browser_env -m openai/gpt-4.1 -k PRIME_API_KEY
```
## Operational notes
By default, CUA mode deploys a sandbox server using a pre-built Docker image ([`deepdream19/cua-server:latest`](https://hub.docker.com/r/deepdream19/cua-server)) that exposes Browserbase's CDP framework, [Understudy](https://github.com/browserbase/stagehand/tree/main/packages/core/lib/v3/understudy). This is the recommended setup.
For local development, you can run the CUA server yourself and disable the sandbox:
```bash theme={null}
prime eval run browser-cua-example \
-m openai/gpt-4.1 \
-k PRIME_API_KEY \
-a '{"use_sandbox": false, "server_url": "http://localhost:3000"}'
```
Enable [Proxies](/platform/identity/proxies) and [Verified](/platform/identity/overview) via environment args:
```bash theme={null}
prime eval run browser-dom-example \
-m openai/gpt-4.1 \
-k PRIME_API_KEY \
-a '{"proxies": true, "verified": true}'
```
These are passed through to Browserbase session creation.
**DOM mode** requires:
* `BROWSERBASE_API_KEY`: Browserbase API key
* `MODEL_API_KEY`: API key for Stagehand's underlying model
**CUA mode** requires:
* `BROWSERBASE_API_KEY`: Browserbase API key
* `PRIME_API_KEY`: Required when using sandbox mode (default). Set via `prime login` or as an env var.
**CUA mode** optional:
* `OPENAI_API_KEY`: Forwarded into the sandbox container if set
## Related resources
Full documentation on Prime's evaluation workflow
Source code and docs for verifiers environments
Core Browserbase documentation
Wire BrowserEnv into Prime RL training workflows
# Prime Intellect
Source: https://docs.browserbase.com/integrations/prime-intellect/introduction
Run Browserbase-backed browser environments for AI agent evaluation and RL training with Prime Intellect.
Browserbase integrates with [Prime Intellect](https://www.primeintellect.ai/) through [`BrowserEnv`](https://github.com/PrimeIntellect-ai/verifiers/blob/main/verifiers/envs/integrations/browser_env/browser_env.py) to provide the infrastructure for evaluating and training state-of-the-art browser agents.
`BrowserEnv` connects Prime's evaluation and RL training pipelines to Browserbase's cloud browsers, giving your agents real browser sessions to interact with and requiring no local browser setup.
Use it to benchmark agent performance on web tasks, generate reward signals for reinforcement learning, and train models that can navigate, extract, and act on live web pages.
How it works:
`BrowserEnv` supports two modes:
The agent receives structured DOM content (HTML/accessibility tree) and issues high-level instructions. Recommended default for most tasks.
The agent receives screenshots and uses coordinate-based actions (click, type, scroll). Best for pixel-grounded or visual tasks.
## When you'd use this
* **Benchmark browser agents**: Run standardized browser tasks against your model and collect reward signals using `prime eval`.
* **Train browser-capable models with RL**: Use Browserbase sessions as the environment in Prime's Hosted Training or self-managed `prime-rl` pipelines.
* **Run browser tasks in Prime's environment workflow**: Plug `BrowserEnv` into any verifiers-compatible evaluation or training loop.
## How it fits together
| Component | Role |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| **Browserbase** | Cloud browser infrastructure: creates and manages browser sessions via API |
| **verifiers `BrowserEnv`** | Environment abstraction that wraps Browserbase sessions for evaluation and training |
| **Prime CLI / Hosted Training** | Orchestrates evaluation runs (`prime eval`) and RL training loops |
Browserbase handles the browsers. `BrowserEnv` translates between Browserbase sessions and the verifiers environment interface. Prime orchestrates the workflow.
## Guides
Run browser agent evaluations with prime eval.
Wire BrowserEnv into Prime RL training workflows.
# Training browser agents with Prime Intellect
Source: https://docs.browserbase.com/integrations/prime-intellect/rl-training
Wire BrowserEnv into Prime Intellect's RL training workflows for browser-capable models.
Once you've validated your browser environment with [evaluations](/integrations/prime-intellect/evals), you can use `BrowserEnv` in Prime Intellect's reinforcement learning pipelines. This guide covers how to wire Browserbase-backed environments into Hosted Training and self-managed `prime-rl` setups.
This guide focuses on connecting `BrowserEnv` to Prime's training workflows. For RL concepts, reward shaping, and training configuration details, see [Prime's training documentation](https://docs.primeintellect.ai/hosted-training/getting-started).
## Before you train
1. **Validate with evals first.** Run `prime eval run` against your environment and verify that reward signals are meaningful. Invest time in understanding and perfecting your judge rubric; your model will only learn if it has a good judge producing accurate reward signals. Training on a broken reward function wastes compute.
2. **Check reward distribution.** Ensure your environment produces a useful range of rewards, not all zeros or all ones.
3. **Test at small scale.** Start with a short training run to confirm the environment, credentials, and networking all work before committing to a full run.
## Setup
### Install and configure the Prime CLI
```bash theme={null}
uv add prime
prime login
```
You'll also need to install the correct dependencies:
```bash theme={null}
uv pip install -e ".[browser]"
```
### Install your BrowserEnv environment
```bash theme={null}
prime lab setup
prime env install browser-dom-example
```
### Set Browserbase credentials
The training workers need access to Browserbase. Export your credentials:
```bash theme={null}
export BROWSERBASE_API_KEY=your_browserbase_api_key
```
## Hosted training
With Prime's Hosted Training, you define your environment in a TOML config and Prime handles orchestration.
### Minimal config
```toml theme={null}
model = "Qwen/Qwen3-4B-Instruct-2507"
max_steps = 50
batch_size = 128
rollouts_per_example = 8
[sampling]
max_tokens = 512
[[env]]
id = "browserbase/webvoyager"
```
The `[[env]]` section references a published Browserbase environment from the Prime hub. Environment-specific args can be passed via `[env.args]`, the same args you'd pass via `-a` in `prime eval run`.
### Launch training
```bash theme={null}
prime rl run training_config.toml
```
Once training completes, you can deploy post-trained models on Prime infrastructure with the selected checkpoint.
Browserbase credentials (`BROWSERBASE_API_KEY`) are read from environment variables by the training workers. Set them in your training environment configuration or pass them as secrets through Prime's secret management. Don't put them in the TOML config file.
## Self-managed prime-rl
For full control over the training loop, use `prime-rl` directly.
### Setup
```bash theme={null}
prime lab setup --prime-rl
uv add verifiers[browser]
```
### Run training
```bash theme={null}
prime rl run rl_config.toml
```
Refer to the [prime-rl documentation](https://docs.primeintellect.ai/training/prime-rl) for full configuration options.
## Choosing DOM vs CUA for training
| | DOM Mode | CUA Mode |
| -------------------- | --------------------------------------------------------- | ------------------------------------------------------------ |
| **Observation** | Structured DOM / accessibility tree | Screenshots |
| **Action space** | Text-based instructions (navigate, act, observe, extract) | Coordinate-based (click, type, scroll, screenshot) |
| **Best for** | Instruction-following tasks, form filling, navigation | Visual/pixel-grounded tasks, complex UIs |
| **Compute cost** | Lower: text observations are compact | Higher: screenshot rendering and transfer |
| **Recommended when** | Your model processes text and you want faster rollouts | Your model is vision-based or you need pixel-level grounding |
## Performance and cost considerations
Each training step involves creating or reusing a browser session, loading a page, and exchanging observations/actions. This adds latency compared to text-only environments. Budget for slower rollouts in your training timeline.
Every rollout step consumes Browserbase session time. Monitor your usage on the [Browserbase dashboard](https://www.browserbase.com/overview) and factor session costs into your training budget. See [Measuring Usage](/optimizations/cost/measuring-usage) for tracking details.
CUA mode is heavier than DOM mode. Screenshots must be rendered, tokenized, and consumed by the model. If your task doesn't require visual grounding, DOM mode will give you faster and cheaper training runs.
## Related resources
Getting started with Prime's Hosted Training
Environment configuration for training workflows
Validate your environment before training
Reduce session costs in production workloads
# Browse CLI
Source: https://docs.browserbase.com/integrations/skills/browse-cli
Use the Browse CLI to drive browser automation, Browserbase cloud APIs, and Browse.sh skills from AI coding agents.
The unified Browserbase CLI now lives under the `browse` command. You can use it for browser automation, Browserbase cloud APIs, Functions, templates, and skill installation.
Use the Browse CLI when you want your agent to stay in the terminal for workflows like:
* **Browser automation**: `browse open`, `snapshot`, `click`, `fill`, `type`, `upload`, `screenshot`, `wait`, and `viewport`
* **Navigation and inspection**: `browse back`, `forward`, `reload`, `highlight`, `refs`, `get`, `is`, `eval`, and `cdp`
* **Session controls**: `browse status`, `stop`, `cursor`, `tab`, `network`, and `mouse`
* **Cloud APIs**: `browse cloud` for Browserbase projects, sessions, contexts, extensions, fetch, and search APIs
* **Functions**: `browse functions` for function development, publishing, and invocation
* **Skills and templates**: `browse skills` and `browse templates` for reusable browser workflows and scaffolds
The `browse` CLI is the current Browserbase CLI surface.
## Choose the right skill
| Skill | Best for |
| ------------ | -------------------------------------------------------------------------------------------------- |
| `browser` | Interactive browser automation powered by the `browse` CLI |
| `fetch` | Lightweight page retrieval via the Browserbase Fetch API |
| `functions` | Focused Browserbase Functions workflows |
| `browse-cli` | Unified `browse` workflows across browser automation, cloud APIs, functions, skills, and templates |
## Installation
```bash theme={null}
npm install -g browse
```
```bash theme={null}
browse skills install
```
```bash theme={null}
npx skills add browserbase/skills --skill browser
```
```bash theme={null}
npm install -g browse
```
```bash theme={null}
claude plugin marketplace add browserbase/skills
```
```bash theme={null}
claude plugin install browse@browserbase --scope local
```
```bash theme={null}
npm install -g browse
```
## Command surface
The current top-level `browse` topics are:
* `cloud` for Browserbase cloud resources and APIs
* `functions` for Browserbase Functions
* `mouse` for raw mouse coordinate input
* `network` for browser network capture
* `skills` for discovering and installing browser automation skills
* `tab` for tab management
* `templates` for scaffolding Browserbase templates
The current top-level browser commands are:
* `back`, `forward`, `reload`, `open`, and `wait`
* `snapshot`, `refs`, `highlight`, `screenshot`, and `cursor`
* `click`, `fill`, `type`, `upload`, `select`, `key`, and `press`
* `get`, `is`, `eval`, `viewport`, `status`, `stop`, and `cdp`
## Authentication
Remote Browserbase commands require your API key:
```bash theme={null}
export BROWSERBASE_API_KEY="your_api_key"
```
The CLI reads `BROWSERBASE_API_KEY` from the environment on every command, including when a browse daemon is already running. You can export the key at any point or provide it to a single command without restarting the daemon:
```bash theme={null}
BROWSERBASE_API_KEY="your_api_key" browse open https://example.com --remote
```
## Daemon lifecycle
Browser commands start the browse daemon automatically. To start a session when no daemon is running, use the exact command below and add `--local` or `--remote` if you want to choose the browser target explicitly:
```bash theme={null}
browse open
```
If a previous daemon stopped unexpectedly, clean up its state and start a fresh session:
```bash theme={null}
browse stop --force
browse open
```
`browse stop` is idempotent. When no daemon is running, it returns `"stopped": false` and exits successfully with code `0`.
## Common workflows
### Browser automation
```bash theme={null}
browse open https://example.com
browse snapshot
browse click @0-4
browse fill @0-7 "hello@example.com"
browse screenshot
```
### Cloud APIs
```bash theme={null}
browse cloud projects list
browse cloud sessions get
browse cloud contexts create
browse cloud extensions upload ./my-extension.zip
```
### Functions
```bash theme={null}
browse functions init my-function
cd my-function
browse functions dev index.ts
browse functions publish index.ts
browse functions invoke --params '{"url":"https://example.com"}'
```
### Skills
```bash theme={null}
browse skills list
browse skills find reviews
browse skills add yelp.com/extract-reviews
```
## Browse.sh
Browse.sh is an open-source catalog of skills for reliable web tasks.
It gives your agents researched playbooks for hundreds of sites.
## Further reading
View the Browserbase skills source and installation instructions.
Explore the public Browse.sh skill catalog.
# Auto-fix selectors in CI/CD
Source: https://docs.browserbase.com/integrations/skills/github-action
Automatically fix broken CSS selectors using Claude Code and Browserbase in GitHub Actions
When websites change their DOM structure, your browser automations break. Instead of manually debugging selectors, you can use Claude Code with Browserbase to automatically detect and fix these issues in CI/CD.
This GitHub Action creates a Browserbase session, lets Claude Code inspect the live DOM, and opens a PR with fixed selectors.
## The problem
Your automation suddenly stops working because the website changed their CSS classes:
```typescript theme={null}
// broken-automation/scraper.ts - These selectors stopped working!
const stories = document.querySelectorAll('.story-row'); // BROKEN - should be .athing
const title = row.querySelector('.story-title .story-link'); // BROKEN - should be .titleline > a
const score = row.querySelector('.story-score'); // BROKEN - should be .score
```
## The solution
Add a GitHub Action that uses Claude Code with Browserbase to automatically fix it:
```yaml theme={null}
name: Auto-Fix Broken Selectors
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # Weekly check
jobs:
fix-selectors:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install CLIs
run: |
npm install -g browse
npm install -g @anthropic-ai/claude-code
- name: Install Browserbase Skills
run: |
npx skills add browserbase/skills --skill browser -g -a claude-code -y
- name: Create Browserbase Session
id: session
env:
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
run: |
RESPONSE=$(curl -s -X POST https://api.browserbase.com/v1/sessions \
-H "Content-Type: application/json" \
-H "x-bb-api-key: $BROWSERBASE_API_KEY" \
-d "{\"keepAlive\": true}")
echo "session_id=$(echo $RESPONSE | jq -r '.id')" >> $GITHUB_OUTPUT
- name: Fix Selectors with Claude Code
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
BROWSERBASE_API_KEY: ${{ secrets.BROWSERBASE_API_KEY }}
SESSION_ID: ${{ steps.session.outputs.session_id }}
run: |
WS_URL="wss://connect.browserbase.com?sessionId=${SESSION_ID}&apiKey=${BROWSERBASE_API_KEY}"
echo "Fix broken-automation/scraper.ts using browse CLI.
1. browse open https://news.ycombinator.com --ws \"${WS_URL}\"
2. browse snapshot -c
3. Find correct selectors and update the file
4. browse stop" | claude --print --dangerously-skip-permissions
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
commit-message: "fix: update CSS selectors via Claude Code"
branch: auto-fix-selectors-${{ github.run_id }}
title: "[Automated] Fixed broken CSS selectors"
body: "Claude Code connected to Browserbase and fixed the selectors."
```
## Trigger options
The example above uses manual dispatch and a schedule, but you can trigger self-healing from various sources:
Trigger the workflow when your monitoring detects automation failures:
```yaml theme={null}
on:
repository_dispatch:
types: [scraper-failure]
```
Then call the GitHub API from your observability platform (Datadog, PagerDuty, Grafana, etc.):
```bash theme={null}
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/OWNER/REPO/dispatches \
-d '{"event_type": "scraper-failure", "client_payload": {"file": "scrapers/news.ts", "error": "Selector .story-row not found"}}'
```
Access the payload in your workflow:
```yaml theme={null}
- name: Fix Selectors with Claude Code
run: |
echo "Fix ${{ github.event.client_payload.file }}.
Error was: ${{ github.event.client_payload.error }}" | claude --print
```
Trigger via HTTP webhook for integration with any external system:
```yaml theme={null}
on:
repository_dispatch:
types: [fix-selectors]
```
Call from your backend, Slack bot, or any service:
```bash theme={null}
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/OWNER/REPO/dispatches \
-d '{"event_type": "fix-selectors"}'
```
Trigger when another workflow fails:
```yaml theme={null}
on:
workflow_run:
workflows: ["Automation Tests"]
types: [completed]
jobs:
fix-on-failure:
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
# ... self-healing steps
```
Run periodic checks to catch issues proactively:
```yaml theme={null}
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
- cron: '0 9 * * 1' # Monday 9am
```
The most powerful setup combines observability alerts with self-healing. When your monitoring detects increased error rates or missing data, it automatically triggers Claude Code to investigate and fix the issue.
## Required secrets
Add these secrets to your GitHub repository:
| Secret | Description |
| --------------------- | ------------------------------ |
| `ANTHROPIC_API_KEY` | Claude API key for Claude Code |
| `BROWSERBASE_API_KEY` | Your Browserbase API key |
## How it works
The action runs on manual dispatch or a weekly schedule.
Installs the browse CLI, Claude Code, and Browserbase skills.
Creates a cloud browser session via Browserbase API.
Claude Code connects to the browser, inspects the live DOM with `browse snapshot`, and identifies correct selectors.
Updates the code and opens a pull request with the changes.
## The result
Claude Code automatically:
1. Opens a real browser via Browserbase
2. Inspects the current DOM structure
3. Identifies the correct selectors
4. Updates your code and opens a PR
This pattern works for any web automation that breaks due to DOM changes, including login flows, data extraction, form submissions, and more.
# Skills
Source: https://docs.browserbase.com/integrations/skills/introduction
Extend your AI coding agents with browser agents, Browserbase Functions, and Browse CLI workflows using Browserbase Skills
Agent Skills are modular capabilities that extend AI agents with domain-specific expertise. Skills package instructions, workflows, and best practices that agents can use automatically when relevant to your request.
The Browserbase skills teach AI coding agents like Claude Code how to use Browserbase across four workflow styles:
* interactive browser automation with the `browse` CLI
* lightweight page fetching via the Browserbase Fetch API
* Browserbase Functions deployment workflows
* unified Browse CLI workflows for cloud APIs, templates, and skill installs
Skills work by providing structured guidance to AI agents, enabling them to perform complex browser automation tasks through natural language commands.
## Key features
Create and run browser automations using the `browse` CLI with interactive sessions, snapshots, and screenshots
Identify and correct broken CSS selectors and failing automations by exploring live pages
Handle login flows for protected sites with guided authentication workflows
Retrieve page content, headers, and metadata. A fast and lightweight complement to browser sessions
Deploy browser agents as Browserbase Functions that run on a schedule or via webhook
Use the unified `browse` CLI for browser automation, cloud resources, functions, templates, and skill installs
## Installation
The [Vercel Skills CLI](https://github.com/vercel-labs/skills) provides a simple way to install skills across multiple AI coding agents including Claude Code, Cursor, and others.
Run one of the following commands depending on the skill you want:
```bash theme={null}
npx skills add browserbase/skills --skill browser
npx skills add browserbase/skills --skill fetch
npx skills add browserbase/skills --skill functions
```
The CLI will prompt you to select your agent and confirm installation.
```bash theme={null}
npm install -g browse
```
Install the Browserbase skill directly from the Claude Code plugin marketplace.
```bash theme={null}
claude plugin marketplace add browserbase/skills
```
```bash theme={null}
claude plugin install browse@browserbase --scope local
claude plugin install fetch@browserbase --scope local
claude plugin install functions@browserbase --scope local
```
```bash theme={null}
npm install -g browse
```
If you use [OpenClaw](https://github.com/openclaw/openclaw), you can install the Browserbase browser skill from [ClawHub](https://clawhub.ai/pkiv/browse):
```bash theme={null}
clawhub install browse
```
Configure your API key for remote browser sessions. Browserbase infers the Project ID from your API key.
```bash theme={null}
openclaw browserbase setup
```
This stores credentials in `~/.openclaw/openclaw.json` and automatically bridges them to the `browse` CLI.
```bash theme={null}
npm install -g browse
```
## Try it out
Once installed, you can use the skill with your AI coding agent. Try these example commands:
### Explore a website
Ask your agent to explore a website and understand its structure:
```
Use the browse skill to explore https://news.ycombinator.com and identify the selectors for story titles and scores
```
### Run Browse CLI workflows
Ask your agent to use the Browse CLI directly:
```
Use the browse CLI skill to open https://news.ycombinator.com, take a snapshot, and identify the selector for each story title
```
## Further reading
Install and use the `browse` CLI with Claude Code, Vercel Skills CLI, and Browse.sh
View source, report issues, and contribute
# Introduction
Source: https://docs.browserbase.com/integrations/stripe/introduction
Give your AI agent a responsible credit card to use for online payments.
By the end of this guide, your **Browserbase** agent will retrieve a virtual card from **Stripe** and make a donation to the Red Cross.
You'll learn how to:
1. **Generate a virtual credit card** using the Stripe API with spending controls.
2. Use Browserbase to **make purchases** with the virtual card.
This integration is useful for:
* Creating a restricted credit card for your AI agent to use for online payments.
* Retrieving card details from Stripe.
* Automating the input of card details into a web form.
## Prerequisites
Before you start, make sure you have:
* [Stripe API key](https://dashboard.stripe.com/apikeys)
* Stripe access to [create a virtual card](https://dashboard.stripe.com/issuing/cards)
* [Browserbase API key](https://browserbase.com/settings/)
* *(Optional)* LLM API key of your choice to use with [Stagehand](https://www.stagehand.dev/).
Next, dive into **creating a virtual card with the Stripe API**.
## Quickstart guides
Learn how to create virtual cards, set spending controls, and automate payments using **Stripe, Playwright, and Browserbase** in Node.js.
Get started with **Stripe, Playwright, and Browserbase** in Python to generate
virtual cards and automate online transactions.
Use **Stagehand AI with Playwright** to automate credit card creation, transactions, and online payments with ease.
Follow the quickstart guide to create a virtual card and make a purchase.
# Agentic credit card automation
Source: https://docs.browserbase.com/integrations/stripe/quickstart
Effortlessly create virtual cards with Stripe and automate purchases using Browserbase.
Here are the steps:
1. Set up your environment
2. Create a Stripe Cardholder
3. Create a Virtual Card with spending controls
4. Retrieve Virtual Card Details
5. Make a Purchase
## Build an agentic credit card automation
### 1. Set up your environment
Install the required dependencies and set up your API keys.
```bash npm theme={null}
npm install stripe @browserbasehq/sdk playwright-core dotenv
```
```bash yarn theme={null}
yarn add stripe @browserbasehq/sdk playwright-core dotenv
```
```bash pnpm theme={null}
pnpm add stripe @browserbasehq/sdk playwright-core dotenv
```
```bash bun theme={null}
bun add stripe @browserbasehq/sdk playwright-core dotenv
```
```bash pip theme={null}
pip install stripe browserbase playwright python-dotenv
```
```bash poetry theme={null}
poetry add stripe browserbase playwright python-dotenv
```
```bash uv theme={null}
uv pip install stripe browserbase playwright python-dotenv
```
```bash conda theme={null}
conda install -c conda-forge stripe browserbase playwright python-dotenv
```
Create a .env file with your API keys (securely store keys for Stripe, Browserbase, and optional AI models for Stagehand):
* Stripe [API key](https://dashboard.stripe.com/apikeys)
* Browserbase [API key](https://browserbase.com/settings/)
* (Optional) LLM API key for [Stagehand](https://www.stagehand.dev/)
```env .env theme={null}
STRIPE_API_KEY=your_stripe_api_key
BROWSERBASE_API_KEY=your_browserbase_api_key
ANTHROPIC_API_KEY=OPTIONAL_your_anthropic_api_key
OPENAI_API_KEY=OPTIONAL_your_openai_api_key
```
### 2. Create a Stripe cardholder
You must create a cardholder before issuing virtual cards. The cardholder will have a verified billing address and be eligible to receive virtual cards.
```ts create-cardholder.ts theme={null}
import Stripe from 'stripe';
import dotenv from 'dotenv';
dotenv.config();
const stripe = new Stripe(process.env.STRIPE_API_KEY!);
async function createCardholder() {
const cardholder = await stripe.issuing.cardholders.create({
name: "Browserbase User",
email: "hello@browserbase.com",
phone_number: "+15555555555",
status: 'active',
type: 'individual',
billing: {
address: {
line1: '123 Main Street',
city: 'San Francisco',
state: 'CA',
country: 'US',
postal_code: '94111',
}
},
});
console.log("Cardholder created:", cardholder.id);
return cardholder;
}
const cardholder = createCardholder();
```
```python create-cardholder.py theme={null}
import stripe
import os
import dotenv
dotenv.load_dotenv()
stripe.api_key = os.getenv("STRIPE_API_KEY")
def create_cardholder():
cardholder = stripe.issuing.Cardholder.create(
name="Browserbase User",
email="hello@browserbase.com",
phone_number="+15555555555",
status='active',
type='individual',
billing={
"address": {
"line1": "123 Main Street",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94111",
}
},
)
print("Cardholder created:", cardholder.id)
return cardholder
card_holder = create_cardholder()
```
Save the cardholder ID from the console output for the next step.
### 3. Create a virtual card
Once you have a cardholder, you can create a virtual card under their name. This step generates a virtual card with a predefined spending limit. Stripe lets you customize the card's spending controls, including daily, monthly, or per-transaction limits. Find more information on spending controls in the [Stripe docs](https://docs.stripe.com/issuing/controls/spending-controls).
```ts create-card.ts theme={null}
async function createCard(cardholderId: string) {
const card = await stripe.issuing.cards.create({
cardholder: cardholderId,
currency: 'usd',
type: 'virtual',
spending_controls: {
allowed_categories: ['charitable_and_social_service_organizations_fundraising'],
// Choose to block certain categories instead of allowing them
// blocked_categories: ['automated_cash_disburse'],
spending_limits: [{
amount: 7500, // $75.00 measured in cents
interval: 'daily', // all_time, daily, weekly, monthly, yearly, per_authorization
}],
},
});
console.log('Card created:', card.id);
return card;
}
const cardholderId = "ic_INPUT_CARDHOLDER_ID_HERE" // replace with your cardholder id from the previous step
const virtual_card = createCard(cardholderId);
```
```python create-card.py theme={null}
def create_card(cardholder_id):
card = stripe.issuing.Card.create(
cardholder=cardholder_id,
currency='usd',
type='virtual',
spending_controls={
"allowed_categories": ['charitable_and_social_service_organizations_fundraising'],
# Choose to block certain categories instead of allowing them
# "blocked_categories": ['automated_cash_disburse'],
"spending_limits": [
{
"amount": 7500, # measured in cents
"interval": "daily", # all_time, daily, weekly, monthly, yearly, per_authorization
}
]
},
)
print("Card created:", card.id)
return card
cardholder_id = "ic_INPUT_CARDHOLDER_ID_HERE" # replace with your card id from the previous step
virtual_card = create_card(cardholder_id)
```
This function returns all the details needed to complete an online purchase.
### 4. Retrieve virtual card details
After creating a virtual card, you'll need to retrieve its details (card number, expiration date, and CVC) to use it for transactions. You can use the returned data to automatically enter the card details when needed.
```typescript get-card.ts theme={null}
export async function getCard(cardId: string) {
const card = await stripe.issuing.cards.retrieve(
cardId, {expand: ['number', 'cvc']});
const cardInfo = {
cardholder_firstName: card.cardholder.name.split(' ')[0],
cardholder_lastName: card.cardholder.name.split(' ')[1],
cardholder_email: card.cardholder.email,
cardholder_phone: card.cardholder.phone_number,
cardholder_address: card.cardholder.billing.address,
card_number: card.number,
expiration_month: card.exp_month,
expiration_year: card.exp_year.toString().slice(-2), // 2028 -> 28
cvc: card.cvc,
brand: card.brand,
currency: card.currency,
};
console.log('Card info:', cardInfo);
return cardInfo;
}
const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step
getCard(cardId);
```
```python get-card.py theme={null}
def getCard(card_id):
card = stripe.issuing.Card.retrieve(card_id, expand=['number', 'cvc'])
card_info = {
'cardholder_firstName': card.cardholder.name.split(' ')[0],
'cardholder_lastName': card.cardholder.name.split(' ')[1],
'cardholder_email': card.cardholder.email,
'cardholder_phone': card.cardholder.phone_number,
'cardholder_address': card.cardholder.billing.address,
'card_number': card.number,
'expiration_month': card.exp_month,
'expiration_year': str(card.exp_year)[-2:], # 2028 -> 28
'cvc': card.cvc,
'brand': card.brand,
'currency': card.currency,
}
print('Card info:', card_info)
return card_info
card_id = "ic_REPLACE_WITH_YOUR_CARD_ID_HERE" # replace with your card id from the previous step
card_info = getCard(card_id)
```
### 5. Make a purchase
In this step, you'll automate filling in the credit card payment form. This example walks you through navigating to the Red Cross donation page, selecting a donation amount, and completing the payment process using the virtual card details retrieved earlier.
```ts Stagehand theme={null}
import { Stagehand } from "@browserbasehq/stagehand";
import dotenv from "dotenv";
import { getCard } from "./get-card.js";
dotenv.config();
const stagehand = new Stagehand({
env: "BROWSERBASE",
});
const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step
async function main() {
await stagehand.init();
const page = stagehand.context.pages()[0];
console.log(`Watching session: https://www.browserbase.com/sessions/${stagehand.browserbaseSessionID}`);
const paymentInfo = await getCard(cardId);
// Navigate to Red Cross donation page
await page.goto('https://www.redcross.org/donate/donation.html/')
const donationAmount = await stagehand.observe(
"Find the donation amounts, and click $75"
);
// Click the first donation amount
await stagehand.act(donationAmount[0])
// Find the continue button and click it
const continueButton = await stagehand.observe(
"Find the continue button and click it"
);
await stagehand.act(continueButton[0])
// Find the credit card button and click it
const creditCardButton = await stagehand.observe(
"Find the credit card button and click it"
);
await stagehand.act(creditCardButton[0])
await stagehand.act("click the continue button")
const formValues = await stagehand.observe(
`Fill in the form with the following values: ${JSON.stringify(paymentInfo)}`
);
console.log("formValues", formValues);
// Fill in the form with the values
for (const value of formValues) {
await stagehand.act(value);
}
await page.waitForTimeout(10000);
// Click the submit button
await stagehand.act("click the donate button")
await stagehand.close();
}
main().catch(console.error);
```
```ts Playwright theme={null}
import dotenv from 'dotenv';
dotenv.config();
import { chromium } from "playwright-core";
import Browserbase from "@browserbasehq/sdk";
import { getCard } from './get-card.ts';
const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;
const cardId = "ic_INPUT_CARD_ID_HERE"; // replace with your card id from the previous step
(async () => {
const bb = new Browserbase({apiKey: BROWSERBASE_API_KEY});
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
const paymentInfo = await getCard(cardId);
// log session ID url
console.log(`Session URL: https://www.browserbase.com/sessions/${session.id}`);
// go to the donation page
await page?.goto("https://www.redcross.org/donate/donation.html");
// click the first donation amount
await page?.click("#modf-handle-0-radio");
// click the continue button
await page?.click("text=Continue");
// click the credit card button
await page?.click("text=credit card");
// Fill billing information
await page?.fill("input[name='bill_to_forename']", paymentInfo.cardholder_firstName!);
await page?.fill("input[name='bill_to_surname']", paymentInfo.cardholder_lastName!);
await page?.fill("input[name='bill_to_email']", paymentInfo.cardholder_email!);
await page?.fill("input[name='bill_to_phone']", paymentInfo.cardholder_phone!);
// Fill in the address information
await page?.fill("input[name='bill_to_address_line1']", paymentInfo.cardholder_address.line1!);
await page?.fill("input[name='bill_to_address_city']", paymentInfo.cardholder_address.city!);
await page?.fill("input[name='bill_to_address_postal_code']", paymentInfo.cardholder_address.postal_code!);
await page?.selectOption("select#bill_to_address_state", paymentInfo.cardholder_address.state!);
// Fill in the card information
await page?.fill("input#cardnumber", paymentInfo.card_number!);
await page?.fill("input#MM", paymentInfo.expiration_month!.toString());
await page?.fill("input#YY", paymentInfo.expiration_year!.toString());
await page?.fill("input#CVC", paymentInfo.cvc!.toString());
// click donate button
await page?.click("text=Donate");
await page.close();
await browser.close();
})().catch((error) => console.error(error.message));
```
```python Playwright theme={null}
from playwright.sync_api import Playwright, sync_playwright
from browserbase import Browserbase
from get_card import getCard
import os
import dotenv
dotenv.load_dotenv()
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
cardId = "ic_INPUT_CARD_ID_HERE" # replace with your card id from the previous step
def run(playwright: Playwright) -> None:
session = bb.sessions.create()
browser = playwright.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
payment_info = getCard(cardId)
# Watch the session
print(f"Session URL: https://browserbase.com/sessions/{session.id}")
# Navigate to the donation page
page.goto("https://www.redcross.org/donate/donation.html")
# Perform actions on the donation page
page.click("#modf-handle-0-radio")
page.click("text=Continue")
page.click("text=credit card")
# Fill billing information
page.fill("input[name='bill_to_forename']", payment_info["cardholder_firstName"])
page.fill("input[name='bill_to_surname']", payment_info["cardholder_lastName"])
page.fill("input[name='bill_to_email']", payment_info["cardholder_email"])
page.fill("input[name='bill_to_phone']", payment_info["cardholder_phone"])
# Fill in the address information
page.fill("input[name='bill_to_address_line1']", payment_info["cardholder_address"]["line1"])
page.fill("input[name='bill_to_address_city']", payment_info["cardholder_address"]["city"])
page.fill("input[name='bill_to_address_postal_code']", payment_info["cardholder_address"]["postal_code"])
page.select_option("select#bill_to_address_state", payment_info["cardholder_address"]["state"])
# Fill in the card information
page.fill("input#cardnumber", payment_info["card_number"])
page.fill("input#MM", str(payment_info["expiration_month"]))
page.fill("input#YY", str(payment_info["expiration_year"]))
page.fill("input#CVC", str(payment_info["cvc"]))
# Click donate button
page.click("text=Donate")
page.close()
browser.close()
if __name__ == "__main__":
with sync_playwright() as playwright:
run(playwright)
```
🎉 You made an online purchase with **Stripe** and **Browserbase**!
## Next steps
With this foundation, you can build more advanced payment automation:
* Create multiple virtual cards for different departments or spending categories
* Integrate with expense management systems
* Set up automated subscription payments
* Build checkout automation for testing payment flows
## Best practices
* **Track your transactions**: Monitor card usage through the [Stripe Dashboard](https://dashboard.stripe.com/cards)
* **Handle errors gracefully**: Implement robust error handling for form fields and payment rejections
* **Add verification steps**: Verify successful transactions by checking for confirmation elements
* **Secure your credentials**: Never expose API keys in client-side code
# Introduction
Source: https://docs.browserbase.com/integrations/temporal/introduction
Resilient browser automation with Temporal workflows and Stagehand
The **Temporal integration** demonstrates how to build bulletproof browser automation using [Temporal's](https://temporal.io/) durable execution platform combined with [Stagehand's](https://docs.stagehand.dev/) AI-powered browser agent capabilities.
This integration showcases best practices for handling browser automation failures with automatic retries, atomic activities, and idempotent operations that ensure reliable execution even when individual steps fail.
Each task is a single, well-defined operation that can be independently retried
Temporal handles failures with intelligent retry policies tailored to each activity
Workflows survive crashes, network failures, and infrastructure issues
Complete visibility into workflow execution through Temporal's Web UI
## Why Temporal + Stagehand?
### The problem with traditional browser automation
Browser automation is inherently unreliable. Networks fail, pages change, elements don't load, and browsers crash. Traditional approaches require complex error handling throughout your code, making it hard to maintain and debug.
### The Temporal solution
Temporal abstracts away the complexity of failure handling:
* **Automatic Retries**: Failed activities are automatically retried with configurable policies
* **State Management**: Workflow state is preserved across failures and restarts
* **Observability**: Complete execution history and real-time monitoring
* **Scalability**: Distribute work across multiple workers automatically
## Use cases
Build reliable data extraction workflows that handle site changes and network issues
Create robust browser tests that retry failed steps instead of failing entire suites
Automate website monitoring with intelligent failure recovery
Transfer data between systems with guaranteed completion despite failures
## What you'll learn
By exploring this integration, you'll master:
* **Temporal workflow design**: How to structure durable, resilient workflows
* **Activity best practices**: Writing atomic, idempotent operations
* **Retry strategy design**: Configuring optimal retry policies for different scenarios
* **Browser session management**: Handling browser lifecycle in distributed systems
* **Error recovery patterns**: Building fault-tolerant automation systems
* **Production monitoring**: Using Temporal's observability tools effectively
This integration serves as a template for any browser automation that needs to be production-ready and fault-tolerant.
**Need simpler serverless automation?** For straightforward workflows that don't require Temporal's advanced orchestration features, [Functions](/platform/runtime/overview) provide a lightweight serverless solution with automatic session management built in.
## Next steps
Ready to build bulletproof browser automation? Start with the quickstart guide to get the Temporal integration running locally.
Set up the Temporal integration in minutes
Learn more about Temporal workflows
# Quickstart
Source: https://docs.browserbase.com/integrations/temporal/quickstart
Get production-ready browser agents with automatic retries and failure recovery
A production-ready browser automation system that can handle failures gracefully using Temporal's workflow orchestration and Browserbase's cloud browsers.
## Before you start
Ensure you have these requirements ready:
Required runtime environment
Install: `brew install temporal`
Browserbase API key
**Required**: The Temporal CLI must be installed and available in your PATH before proceeding.
## Step 1: Project setup
### Clone and install
```bash theme={null}
# Clone the integration template
npx degit browserbase/integrations/examples/integrations/temporal browserbase-temporal
cd browserbase-temporal
# Install all dependencies
npm install
# Install browser binaries
npx playwright install
```
**Core Temporal packages:**
* `@temporalio/worker` - Workflow execution engine
* `@temporalio/workflow` - Workflow definitions
* `@temporalio/activity` - Activity implementations
**Browser automation:**
* `@browserbasehq/stagehand` - AI-powered browser control
* `playwright` - Browser automation engine
**AI integration:**
* `@anthropic-ai/sdk` - Claude API client
* `zod` - Schema validation
## Step 2: Configuration
### Environment variables
Create your `.env` file with the required API keys:
```bash theme={null}
# Browserbase Configuration (Required)
BROWSERBASE_API_KEY=your_browserbase_api_key_here
# AI Provider (Required)
OPENAI_API_KEY=your_openai_api_key_here
# Temporal Configuration (Optional)
TEMPORAL_ADDRESS=localhost:7233
TEMPORAL_NAMESPACE=default
# Worker Configuration (Optional)
MAX_CONCURRENT_ACTIVITIES=2
TASK_QUEUE=browser-automation
```
## Step 3: Start Temporal Server
### Launch development server
```bash theme={null}
temporal server start-dev
```
**Keep this terminal open** - The Temporal server must run continuously during development.
## Step 4: Run your first workflow
### Start the worker process
In a **new terminal** (keep Temporal server running):
```bash theme={null}
npm run worker
```
**Expected output:**
```bash theme={null}
Polling for tasks on queue: browser-automation
Worker ready with activities: initializeBrowser, navigateToSearchPage, executeSearch, extractSearchResults, cleanupBrowser, formatResults
```
### Execute a search workflow
In a **third terminal**, run the demo:
```bash theme={null}
# Basic search
npm run demo
# Custom search query
npm run demo "Temporal workflow patterns"
```
**What happens:**
1. Creates a new workflow execution
2. Provides monitoring URL for real-time tracking
3. Executes browser automation with automatic retries
4. Returns structured search results
## Monitoring and debugging
### Using Temporal web UI
Click the monitoring URL provided when starting a workflow:
```
http://localhost:8233/namespaces/default/workflows/resilience-test-1640995200000
```
The Web UI shows:
* **Visual timeline** of activity execution
* **Event history** with detailed logs
* **Input/output data** for each activity
* **Retry attempts** and failure reasons
### Testing resilience
The integration includes built-in failure simulation for testing:
```typescript Network Failure Simulation theme={null}
// Each activity has 15% chance of network failure
function simulateNetworkDisconnect(stage: string): void {
if (Math.random() < 0.15) {
const failures = [
'ECONNRESET: Connection reset by peer',
'ETIMEDOUT: Connection timed out',
'ENOTFOUND: DNS lookup failed',
'ECONNREFUSED: Connection refused'
];
throw new Error(`Network failure during ${stage}: ${failures[Math.floor(Math.random() * failures.length)]}`);
}
}
```
```bash Testing Multiple Scenarios theme={null}
# Run several times to trigger different failure patterns
npm run demo "test query 1"
npm run demo "test query 2"
npm run demo "test query 3"
# Watch the Web UI to observe retry behavior
```
## Troubleshooting guide
**Error**: `Error: 14 UNAVAILABLE: failed to connect to all addresses`
**Root Cause**: Temporal server not running or port conflicts
**Solutions**:
1. Start Temporal server: `temporal server start-dev`
2. Check port 7233 is available: `lsof -i :7233`
3. Kill conflicting processes if needed
4. Restart the server completely
**Error**: `Worker failed to start` or connection timeouts
**Root Cause**: Worker cannot connect to Temporal server
**Solutions**:
1. Ensure Temporal server started successfully first
2. Verify task queue name: `browser-automation`
3. Check network connectivity to localhost:7233
4. Restart worker after server is stable
**Error**: `Failed to initialize browser` or Browserbase errors
**Root Cause**: Invalid API credentials or quota limits
**Solutions**:
1. Verify your API key in `.env`
2. Check Browserbase account credits/quota
3. Test API credentials with `curl` request
4. Try a different Browserbase project
**Error**: `Extraction returned null values` or validation failures
**Root Cause**: AI API issues or rate limiting
**Solutions**:
1. Verify Anthropic API key validity
2. Check API rate limits and credits
3. Simplify extraction instructions
4. Consider switching to OpenAI as fallback
**Error**: Workflows hang or don't progress
**Root Cause**: Worker issues or activity timeouts
**Solutions**:
1. Check worker is processing tasks (`npm run worker`)
2. Inspect workflow state in Web UI
3. Look for timeout errors in activity logs
4. Increase timeout values if needed
5. Restart worker if necessary
## What's next?
Now that you have a working Temporal + Browserbase integration:
Create your own workflows for specific automation tasks
Deploy to Temporal Cloud for production workloads
Explore child workflows, signals, and queries
Set up production monitoring and alerting
***
**Need help?** Join the [Temporal Community](https://temporal.io/community) and [Stagehand Slack](https://stagehand.dev/slack) for support and discussions.
# Introduction
Source: https://docs.browserbase.com/integrations/trigger/introduction
Learn what Trigger.dev is, why you might pair it with Browserbase, and what you can build with this integration.
By the end of this guide, your **Trigger.dev** agent will automate a background job that processes data and sends notifications.
You'll learn how to:
1. **Set up a Trigger.dev workflow** to handle long-running tasks.
2. Use Trigger.dev to **schedule and monitor** your background jobs.
> Background jobs meet cloud browsers
Trigger.dev is an **open-source background-jobs & AI infrastructure platform**. It lets you write long-running workflows in plain async/await code without worrying about queues, cron schedulers, retries, or observability. Think **BullMQ + Cron + Sentry + Kubernetes**, but rolled into one developer-first package and available as a hosted SaaS or self-hosted.
Browserbase gives you **disposable, headless Chrome instances over WebSockets**, perfect for data extraction, screenshotting, and PDF generation. Combine the two and you unlock **server-side browser automation that never times out**:
* Spin up an isolated browser in Browserbase
* Drive it with Puppeteer/Playwright from a Trigger task
* Stream logs & status back to your UI in real-time via Trigger Realtime
* Scale to thousands of concurrent browsers with zero infra work
## Why use them together?
| Challenge | How the integration helps |
| -------------------------------------------------- | -------------------------------------------------------------------------- |
| Functions on Vercel/Netlify time-out after 10–30 s | Trigger tasks have **no timeouts**, so long extraction jobs finish happily |
| Queuing, retries, rate limits | Built-in `retry`, `concurrency`, and cron features |
| Running Chrome on serverless | Browserbase hosts Chrome. No Lambda layers, no xvfb |
| Observability | Every job is a **run** in Trigger with logs & session recordings |
## What you can build
* **PDF → PNG pipelines** (MuPDF via `aptGet` extension)
* **High-volume data extraction** with rotating proxies
* **Automated report generation** (React-to-PDF, screenshots)
* **AI agents** that browse sites, summarize content and send email
Looking for a concrete example? Jump to the quickstart or browse the code here: [Repo](https://github.com/browserbase/integrations/tree/master/examples/integrations/trigger)
**Don't need full background job infrastructure?** [Functions](/platform/runtime/overview) provide a simpler option for browser agents with API invocation, without managing Trigger.dev infrastructure. Ideal for straightforward agent workflows.
***
Follow the quickstart guide to automate a background job.
# Quickstart
Source: https://docs.browserbase.com/integrations/trigger/quickstart
Spin up your first Trigger.dev job that controls a Browserbase browser in under 5 minutes.
> Prerequisites: Node 18+, PNPM/NPM/Yarn, a **Trigger.dev** account, and a **Browserbase** API key.
## 1. Scaffold a fresh project
```bash theme={null}
mkdir my-trigger-project && cd $_
npm init -y # or pnpm init / yarn init
```
Add required packages:
```bash theme={null}
npm install @trigger.dev/sdk @trigger.dev/build puppeteer puppeteer-core
```
If you want TypeScript (recommended):
```bash theme={null}
npm install -D typescript ts-node @types/node
npx tsc --init
```
## 2. Create `trigger.config.ts`
```ts title="trigger.config.ts" theme={null}
import { defineConfig } from "@trigger.dev/sdk";
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";
import { aptGet } from "@trigger.dev/build/extensions/core";
export default defineConfig({
project: "your_project_id", // grab from the Trigger dashboard
dirs: ["./src/trigger"],
build: {
extensions: [aptGet({ packages: ["mupdf-tools", "curl"] }), puppeteer()],
},
});
```
This installs MuPDF + Chrome in the build container so your tasks can run `mutool` and Puppeteer.
## 3. Configure `.env.local`
```bash theme={null}
cp .env.example .env.local && $EDITOR .env.local
```
Fill in:
```dotenv theme={null}
TRIGGER_SECRET_KEY=tr_dev_***
BROWSERBASE_API_KEY=bb_***
S3_ENDPOINT=https://.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
S3_BUCKET=my-bucket
```
## 4. Add your first task `src/trigger/puppeteer-log-title.ts`
```tsx title="src/trigger/puppeteer-log-title.ts" theme={null}
import { task } from "@trigger.dev/sdk";
import puppeteer from "puppeteer";
export const logTitle = task({
id: "browserbase-title",
run: async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${process.env.BROWSERBASE_API_KEY}`,
});
const page = await browser.newPage();
await page.goto("https://example.com");
const title = await page.title();
console.log({ title });
await browser.close();
},
});
```
> You can export multiple tasks from this file or additional files in `src/trigger/`. Trigger.dev will pick them up automatically.
## 5. Run locally
Start the Trigger.dev CLI dev server, which watches your task files and connects to the Trigger.dev platform:
```bash theme={null}
npx trigger.dev dev
```
You should see the task register in the [Trigger.dev dashboard](https://cloud.trigger.dev). From there you can test-run the task directly.
## 6. Deploy
```bash theme={null}
npx trigger.dev deploy
```
That's it. Your background browser automation now scales automatically in production.
***
Need something more advanced? Check out:
* **`pdf-to-image.tsx`** – converts multi-page PDFs to PNG and uploads to Cloudflare R2.
* **`summarize-hn.tsx`** – extracts Hacker News articles, feeds them to OpenAI, emails a summary.
Happy triggering!
# Val Town integration
Source: https://docs.browserbase.com/integrations/val-town/introduction
Allow your Val Town script to perform complex tasks on the web.
Val Town is a platform for running JavaScript in a browser environment through serverless functions, called Vals.
The platform also serves as a social website for developers to code in the cloud.
Vals can be anything from a TypeScript playground to a personal automation tool, or the basis for a web API.
Val Town currently only supports JavaScript and TypeScript.
Val Town is free to use, but requires a [paid plan](https://www.val.town/pricing) to access the full range of features.
**Prefer native Browserbase infrastructure?** [Functions](/platform/runtime/overview) offer a similar serverless experience but run directly on Browserbase's infrastructure with built-in session management, local development tools, and Infrastructure-as-Code deployment. Great for complex browser automation workflows.
## Add Browserbase to your Val
Configure Browserbase to add additional web-browsing capabilities to your
Val.
# Tutorial: Slack Scout
Source: https://docs.browserbase.com/integrations/val-town/slack-scout
Send a Slack notification every time your keywords are mentioned on Twitter, Hacker News, or Reddit.
Slack scout sends a Slack notification every time your keywords are mentioned on Twitter, Hacker News, or Reddit. Get notified whenever you, your company, or topics of interest are mentioned online.
Built with [Browserbase](https://browserbase.com) and [Val Town](https://val.town). Inspired by [f5bot.com](https://f5bot.com).
## What this tutorial covers
* Access and extract website posts and contents using Browserbase
* Write scheduled functions and APIs with Val Town
* Send automated Slack messages via webhooks
## Getting started
In this tutorial, you’ll need a
* Browserbase [API key](https://www.browserbase.com/settings)
* Val Town account
* Slack Webhook URL: create it [here](https://docs.val.town/integrations/slack/send-messages-to-slack/)
### Browserbase
[Browserbase](https://www.browserbase.com) is a developer platform to run, manage, and monitor headless browsers at scale. This tutorial uses Browserbase to navigate and extract data from different news sources. It also uses [Browserbase’s Proxies](/platform/identity/proxies) to provide consistent network identity across multiple browser sessions.
[Sign up for free](https://www.browserbase.com/sign-up) to get started!
### Val Town
[Val Town](http://val.town/) is a platform to write and deploy JavaScript. You’ll use Val Town for three things.
1. Create [HTTP scripts](https://docs.val.town/types/http/) that run Browserbase sessions. These Browserbase sessions will execute web automation tasks, such as navigating Hacker News and Reddit.
2. Write [Cron Functions](https://docs.val.town/types/cron/) (like Cron Jobs, but more flexible) that periodically run the HTTP scripts.
3. Store persistent data in the Val Town provided [SQLite database](https://docs.val.town/std/sqlite/). This built-in database lets you track search results, so you only send Slack notifications for new, unrecorded keyword mentions.
[Sign up for free](https://www.val.town/auth/signup?next=%2F) to get started!
### Twitter (X)
For this tutorial, you’ll use the Twitter API to include Twitter post results.
You'll need to create a new Twitter account to use the API. It costs \$100 /
month to have a Basic Twitter Developer account.
Once you have the `SLACK_WEBHOOK_URL`, `BROWSERBASE_API_KEY`, and `TWITTER_BEARER_TOKEN`, input all of these as [Val Town Environment Variables](https://www.val.town/settings/environment-variables).
## Creating the APIs
The same method applies to create scripts that search and extract data from Reddit, Hacker News, and Twitter. First, start with Reddit.
To create a new script, go to [Val Town](http://val.town/) → New → HTTP Val. The script takes in a keyword and returns all Reddit posts from the last day that include that keyword.
For each Reddit post, the output should include the URL, date\_published, and post title.
For example:
```javascript theme={null}
{
source: 'Reddit', // or 'Hacker News' or 'Twitter'
url: 'https://www.reddit.com/r/browsers/comments/vdhge5/browserbase_launched/';
date_published: 'Aug 30, 2024';
title: 'Browserbase just launched';
}
```
In the `redditSearch` script, start by importing Puppeteer and creating a Browserbase session with proxies enabled. Be sure to get your `BROWSERBASE_API_KEY` from your [Browserbase settings](https://www.browserbase.com/settings).
```javascript theme={null}
import puppeteer from "https://deno.land/x/puppeteer@16.2.0/mod.ts";
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://connect.browserbase.com?apiKey=${apiKey}&enableProxy=true`,
ignoreHTTPSErrors: true,
});
```
Next, you’ll want to:
1. Navigate to Reddit and do a keyword search
2. Extract each resulting post
To navigate to a Reddit URL that already has the keyword and search time frame encoded, write a helper function that encodes the query and sets search parameters for data collection.
```javascript theme={null}
function constructSearchUrl(query: string): string {
const encodedQuery = encodeURIComponent(query).replace(/%20/g, "+");
return `https://www.reddit.com/search/?q=${encodedQuery}&type=link&t=day`;
}
const url = constructSearchUrl(query);
await page.goto(url, { waitUntil: "networkidle0" });
```
Once you’ve navigated to the constructed URL, you can extract each search result. For each post, select the `title`, `date_published`, and `url`.
```javascript theme={null}
const posts = document.querySelectorAll("div[data-testid=\"search-post-unit\"]");
return Array.from(posts).map(post => {
const titleElement = post.querySelector("a[id^=\"search-post-title\"]");
const timeElement = post.querySelector("faceplate-timeago");
return {
source: "Reddit",
title: titleElement?.textContent?.trim() || "",
url: titleElement?.href || "",
date_published: timeElement?.textContent?.trim() || "",
};
});
// Example
{
source: 'Reddit', // or 'Hacker News' or 'Twitter'
url: 'https://www.reddit.com/r/browsers/comments/vdhge5/browserbase_launched/';
date_published: '1 day ago';
title: 'Browserbase just launched';
}
```
You’ll notice that Reddit posts return the date\_published in the format of ‘1 day ago’ instead of ‘Aug 29, 2024.’ To make date handling more consistent, create a reusable helper script, `convertRelativeDatetoString`, to convert dates to a uniform date format. Import this at the top of the redditSearch script.
```javascript theme={null}
import { convertRelativeDateToString } from "https://esm.town/v/sarahxc/convertRelativeDateToString";
const date_published = await convertRelativeDateToString({
relativeDate: post.date_published,
});
```
You can see the finished redditSearch code [here](https://www.val.town/v/sarahxc/redditSearch).
Follow a similar process to create `hackerNewsSearch`, and use the Twitter API to create `twitterSearch`.
**See all three scripts here:**
*Reddit* → [redditSearch](https://www.val.town/v/sarahxc/redditSearch)
*Hacker News* → [hackerNewsSearch](https://www.val.town/v/alexdphan/hackerNewsSearch)
*Twitter* → [twitterSearch](https://www.val.town/v/alexdphan/twitterSearch)
## Creating the Cron Function
For the last step, create a `slackScout` cron job that calls `redditSearch`, `hackerNewsSearch`, and `twitterSearch` that runs every hour. To create the cron file, go to [Val Town](http://val.town/) → New → Cron Val.
In the slackScout file, import the HTTP scripts.
```javascript theme={null}
import { hackerNewsSearch } from "https://esm.town/v/alexdphan/hackerNewsSearch";
import { twitterSearch } from "https://esm.town/v/alexdphan/twitterSearch";
import { redditSearch } from "https://esm.town/v/sarahxc/redditSearch";
```
Then create helper functions that call the Reddit, Hacker News, and Twitter HTTP scripts.
```javascript theme={null}
// Fetch Reddit, Hacker News, and Twitter results
async function fetchRedditResults(topic: string): Promise {
return redditSearch({ query: topic });
}
async function fetchHackerNewsResults(topic: string): Promise {
return hackerNewsSearch({
query: topic,
pages: 2,
apiKey: Deno.env.get("BROWSERBASE_API_KEY") ?? "",
});
}
async function fetchTwitterResults(topic: string): Promise {
return twitterSearch({
query: topic,
maxResults: 10,
daysBack: 1,
apiKey: Deno.env.get("TWITTER_BEARER_TOKEN") ?? "",
});
}
```
Next, to store the website results, set up Val Town’s SQLite database. Import SQLite and write three helper functions.
1. `createTable`: creates the new SQLite table
2. `isURLInTable`: for each new website returned, checks if the website is already in the table
3. `addWebsiteToTable`: if `isURLInTable` is `False`, adds the new website to the table
```javascript theme={null}
const { sqlite } = await import("https://esm.town/v/std/sqlite");
const TABLE_NAME = "slack_scout_browserbase";
// Create an SQLite table
async function createTable(): Promise {
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
source TEXT NOT NULL,
url TEXT PRIMARY KEY,
title TEXT NOT NULL,
date_published TEXT NOT NULL
)
`);
}
async function isURLInTable(url: string): Promise {
const result = await sqlite.execute({
sql: `SELECT 1 FROM ${TABLE_NAME} WHERE url = :url LIMIT 1`,
args: { url },
});
return result.rows.length > 0;
}
async function addWebsiteToTable(website: Website): Promise {
await sqlite.execute({
sql: `INSERT INTO ${TABLE_NAME} (source, url, title, date_published)
VALUES (:source, :url, :title, :date_published)`,
args: website,
});
}
```
Finally, write a function to send a Slack notification for each new website.
```javascript theme={null}
async function sendSlackMessage(message: string): Promise {
const slackWebhookUrl = Deno.env.get("SLACK_WEBHOOK_URL");
if (!slackWebhookUrl) {
throw new Error("SLACK_WEBHOOK_URL environment variable is not set");
}
const response = await fetch(slackWebhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
blocks: [
{
type: "section",
text: { type: "mrkdwn", text: message },
},
],
}),
});
if (!response.ok) {
throw new Error(`Slack API error: ${response.status} ${response.statusText}`);
}
return response;
}
```
The main function initiates the workflow, calling helper functions to fetch and process data from multiple sources.
```javascript theme={null}
export default async function(interval: Interval): Promise {
try {
await createTable();
for (const topic of KEYWORDS) {
const results = await Promise.allSettled([
fetchHackerNewsResults(topic),
fetchTwitterResults(topic),
fetchRedditResults(topic),
]);
const validResults = results
.filter((result): result is PromiseFulfilledResult => result.status === "fulfilled")
.flatMap(result => result.value);
await processResults(validResults);
}
console.log("Cron job completed successfully.");
} catch (error) {
console.error("An error occurred during the cron job:", error);
}
}
```
Done! You can see the final `slackScout` [here](https://www.val.town/v/sarahxc/slackScout).
## And that’s it!
Optionally, you can use [Browserbase](https://www.browserbase.com) and [Val Town](http://val.town/) to create additional HTTP scripts to monitor additional websites like Substack, Medium, WSJ, etc. Browserbase has a [list of Vals](https://www.val.town/u/browserbase) you can get started with in your own projects. If you have any questions, concerns, or feedback, reach out to the team.
[support@browserbase.com](mailto:support@browserbase.com)
# Agent Browser integration
Source: https://docs.browserbase.com/integrations/vercel/agent-browser/introduction
Use Agent Browser with Browserbase for fast, CLI-driven browser control optimized for AI agents.
[Agent Browser](https://github.com/vercel-labs/agent-browser) is a headless browser automation CLI built for AI agents. It provides a fast Rust-powered command-line interface with accessibility-first semantics, making it ideal for LLM-driven browser control.
Agent Browser has built-in Browserbase support via its `-p browserbase` provider flag, giving your agents cloud browser sessions with no local browser required.
## Key features
* **Accessibility tree snapshots** with element refs (`@e1`, `@e2`) optimized for LLM consumption
* **Full browser control** via simple CLI commands: click, type, fill, screenshot, navigate
* **Built-in Browserbase provider**: connect to cloud browsers with a single flag
* **Rust-powered performance** with sub-millisecond parsing overhead
* **Semantic locators**: find elements by ARIA role, text, label, placeholder, or test ID
## How it works
```
AI Agent → agent-browser CLI → Browserbase Cloud Browser → Target Website → Data Back
```
Your AI agent calls `agent-browser` commands to control a Browserbase cloud browser. The `snapshot` command returns an accessibility tree with element references that the agent can use for deterministic interactions.
## When to use this
* **AI agent tooling**: give LLMs a CLI interface to control browsers
* **Automated data extraction**: extract data using accessibility tree snapshots
* **Form filling and testing**: interact with web forms via simple commands
* **Screenshot and PDF generation**: capture pages from cloud browsers
## Get started
Set up Agent Browser with Browserbase and start automating in minutes.
# Add cloud browsers to Agent Browser
Source: https://docs.browserbase.com/integrations/vercel/agent-browser/quickstart
Integrate Browserbase with Agent Browser for cloud-hosted browser automation.
Visit the [Dashboard's Settings tab](https://www.browserbase.com/settings):
Copy your API key for the next step.
```bash theme={null}
npm install -g agent-browser
agent-browser install
```
```bash theme={null}
npx agent-browser install
```
```bash theme={null}
brew install agent-browser
agent-browser install
```
The `install` command downloads Chromium for local use. When using the Browserbase provider, the browser runs in the cloud instead.
Set your Browserbase credentials:
```bash theme={null}
export BROWSERBASE_API_KEY="bb_live_..."
```
Use the `-p browserbase` flag to run commands on a Browserbase cloud browser:
```bash theme={null}
agent-browser -p browserbase open https://example.com
```
This creates a Browserbase session and navigates to the URL. You can view the session in your [Browserbase Dashboard](https://www.browserbase.com/sessions).
The `snapshot` command returns an accessibility tree with element references. This is the primary way AI agents understand page content:
```bash theme={null}
agent-browser -p browserbase snapshot
```
Output includes element refs that you can use in subsequent commands:
```
- heading "Example Domain" [ref=e1] [level=1]
- paragraph "This domain is for use in illustrative examples."
- link "More information..." [ref=e3]
```
Use the `-i` flag to show only interactive elements:
```bash theme={null}
agent-browser -p browserbase snapshot -i
```
Use element refs from the snapshot to click, type, and fill:
```bash theme={null}
# Click a link by ref
agent-browser -p browserbase click @e3
# Fill a form field
agent-browser -p browserbase fill @e5 "test@example.com"
# Take a screenshot
agent-browser -p browserbase screenshot page.png
```
```bash theme={null}
agent-browser -p browserbase close
```
All commands support the `-p browserbase` flag to run on Browserbase cloud browsers. You can also set `AGENT_BROWSER_PROVIDER=browserbase` as an environment variable to avoid passing the flag each time. See the [Agent Browser README](https://github.com/vercel-labs/agent-browser) for the full command reference.
## Connecting via CDP
You can also connect Agent Browser to a Browserbase session directly using the CDP WebSocket URL:
```javascript theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
console.log(session.connectUrl);
// wss://connect.browserbase.com/?sessionId=...
```
Then connect Agent Browser:
```bash theme={null}
agent-browser --cdp "wss://connect.browserbase.com/?sessionId=..." snapshot
```
Full documentation, examples, and source code.
Learn more about creating and managing Browserbase sessions.
# BrowseGPT
Source: https://docs.browserbase.com/integrations/vercel/browsegpt
BrowseGPT is a tool that allows you to search the web using a chat interface.
[BrowseGPT](https://www.browsegpt.dev) is a tool that lets you search the web using a chat interface.
It's built on top of the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction) and [Browserbase](https://www.browserbase.com).
## What this tutorial covers
* Access and extract website posts and contents using Browserbase
* Use the Vercel AI SDK to create a chat interface
* Stream the results from the LLM
## Usage
To use BrowseGPT, you need to have the Vercel AI SDK and Browserbase installed.
The following packages are recommended:
* [ai](https://www.npmjs.com/package/ai) for the chat interface
* [zod](https://www.npmjs.com/package/zod) for data validation
* [playwright](https://www.npmjs.com/package/playwright) for data extraction
* [@ai-sdk/openai](https://www.npmjs.com/package/@ai-sdk/openai) for OpenAI models
* [@ai-sdk/anthropic](https://www.npmjs.com/package/@ai-sdk/anthropic) for Anthropic models
* [@mozilla/readability](https://www.npmjs.com/package/@mozilla/readability) for the readability library
* [jsdom](https://www.npmjs.com/package/jsdom) for DOM manipulation
```bash theme={null}
npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/react zod playwright @mozilla/readability jsdom
```
## Getting started
For this tutorial, you'll need:
1. Browserbase credentials:
* [API key](https://www.browserbase.com/settings) (Project ID is optional)
2. An LLM API key from one of the following:
* [OpenAI](https://platform.openai.com/)
* [Anthropic](https://www.anthropic.com/)
* [Any LLM supported by Vercel AI SDK](https://sdk.vercel.ai/docs/introduction#model-providers)
Browserbase sessions often run longer than 15 seconds. By signing up for the
Pro Plan on [Vercel](https://vercel.com/pricing), you can increase the Vercel
function duration limit.
## Imports and dependencies
Nextjs uses [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) to handle API requests.
These include methods such as `GET`, `POST`, `PUT`, `DELETE`, etc.
To create a new route handler, create a new file in the `app/api` directory.
In this example, the file is called `route.ts` for the chat route.
From here, import the necessary dependencies.
```typescript route.ts theme={null}
import { openai } from "@ai-sdk/openai";
import { streamText, convertToModelMessages, tool, generateText } from "ai";
import { z } from "zod";
import { chromium } from "playwright";
import { anthropic } from "@ai-sdk/anthropic";
import { Readability } from "@mozilla/readability";
import { JSDOM } from "jsdom";
```
This section imports necessary libraries and modules for the application.
It includes the Vercel AI SDK, Zod for schema validation, Playwright for web automation, and libraries for content extraction and processing.
## Helper functions
These are utility functions used throughout the application.
`getDebugUrl` fetches debug information for a Browserbase session, while `createSession` initializes a new Browserbase session for web interactions.
```typescript theme={null}
// Get the debug URL for a Browserbase session
async function getDebugUrl(id: string) {
const response = await fetch(
`https://api.browserbase.com/v1/sessions/${id}/debug`,
{
method: "GET",
headers: {
"x-bb-api-key": process.env.BROWSERBASE_API_KEY,
"Content-Type": "application/json",
},
},
);
const data = await response.json();
return data;
}
// Create a new Browserbase session
async function createSession() {
const response = await fetch(`https://api.browserbase.com/v1/sessions`, {
method: "POST",
headers: {
"x-bb-api-key": process.env.BROWSERBASE_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
keepAlive: true,
}),
});
const data = await response.json();
return { id: data.id, debugUrl: data.debugUrl };
}
```
## Main API route handler
This section sets up the main API route handler.
It configures the runtime environment, sets a maximum duration for the API call, and defines the POST method that handles incoming requests.
The Vercel AI SDK's streamText function processes messages and streams responses.
The maximum duration is set to 300 seconds (5 minutes), since Browserbase sessions often run longer than 15 seconds (Vercel's default timeout).
```typescript route.ts theme={null}
// Set the maximum duration to 300 seconds (5 minutes)
export const maxDuration = 300;
// POST method to handle incoming requests
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4.1"),
messages: await convertToModelMessages(messages),
tools: {
// ... (tool definitions)
},
});
return result.toUIMessageStreamResponse();
}
```
## Tools
Next, create the tools needed for this route handler. These tools are used depending on the user's request.
For example, if the user wants to search the web, the `googleSearch` tool handles it. If they want to get the content of a page, the `getPageContent` tool is used.
Keep in mind that you have the option to choose any LLM model that is compatible with the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction#model-providers).
In testing, `gpt-4.1` worked best for tool calling, and `claude-sonnet-4-6` worked best for generating responses.
### Create Browserbase session tool
This tool creates a new Browserbase session. It's used when a fresh browsing context is needed for web interactions.
The tool returns the session ID and debug URL, which are used in subsequent operations.
```typescript theme={null}
createSession: tool({
description: 'Create a new Browserbase session',
inputSchema: z.object({}),
execute: async () => {
const session = await createSession();
const debugUrl = await getDebugUrl(session.id);
return { sessionId: session.id, debugUrl: debugUrl.debuggerFullscreenUrl, toolName: 'Creating a new session'};
},
}),
```
The `createSession()` and `getDebugUrl()` functions from earlier create a new Browserbase session and get the debug URL.
This lets you embed the debug URL in the response so the frontend can display the Browserbase session.
### Google Search tool
This tool performs a web search using Browserbase. It takes a search query as input and returns the search results.
```typescript theme={null}
googleSearch: tool({
description: 'Search Google for a query',
inputSchema: z.object({
// ... (similar parameters as createSession tool)
}),
execute: async ({ query, sessionId }) => {
// ... (debug URL and browser connection setup)
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await page.goto(`https://www.google.com/search?q=${encodeURIComponent(query)}`);
await page.waitForTimeout(500);
await page.keyboard.press('Enter');
await page.waitForLoadState('load', { timeout: 10000 });
await page.waitForSelector('.g');
const results = await page.evaluate(() => {
const items = document.querySelectorAll('.g');
return Array.from(items).map(item => {
const title = item.querySelector('h3')?.textContent || '';
const description = item.querySelector('.VwiC3b')?.textContent || '';
return { title, description };
});
});
const text = results.map(item => `${item.title}\n${item.description}`).join('\n\n');
const response = await generateText({
model: anthropic('claude-sonnet-4-6'),
prompt: `Evaluate the following web page content: ${text}`,
});
return {
toolName: 'Searching Google',
content: response.text,
dataCollected: true,
};
},
}),
```
### Ask for confirmation tool
This tool asks the user for confirmation before performing a specific action.
It takes a confirmation prompt as input and returns the user's response.
```typescript theme={null}
askForConfirmation: tool({
description: 'Ask the user for confirmation.',
inputSchema: z.object({
message: z.string().describe('The message to ask for confirmation.'),
}),
}),
```
### Get page content tool
The last tool is `getPageContent`.
This tool retrieves the content of a web page using [Playwright](https://playwright.dev/). It then uses [jsdom](https://github.com/jsdom/jsdom) to parse the HTML content into a DOM structure and [Readability](https://github.com/mozilla/readability) to extract the main content of the page.
Finally, it uses the [Anthropic Claude](https://www.anthropic.com/) model to generate a summary of the page's content.
```typescript theme={null}
getPageContent: tool({
description: 'Get the content of a page using Playwright',
inputSchema: z.object({
url: z.string().describe('The URL of the page to fetch content from'),
sessionId: z.string().describe('The Browserbase session ID to use'),
}),
execute: async ({ url, sessionId }) => {
// Get debug URL and connect to Browserbase session
const debugUrl = await getDebugUrl(sessionId);
const browser = await chromium.connectOverCDP(debugUrl.debuggerFullscreenUrl);
// Get the default context and page
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
// Navigate to the specified URL
await page.goto(url, { waitUntil: 'networkidle' });
// Get the page content
const content = await page.content();
// Use Readability to extract the main content
const dom = new JSDOM(content);
const reader = new Readability(dom.window.document);
const article = reader.parse();
let extractedContent = '';
if (article) {
// If Readability successfully parsed the content, use it
extractedContent = article.textContent;
} else {
// Fallback: extract all text from the body
extractedContent = await page.evaluate(() => document.body.innerText);
}
// Generate a summary using the Anthropic Claude model
const response = await generateText({
model: anthropic('claude-sonnet-4-6'),
prompt: `Summarize the following web page content: ${extractedContent}`,
});
// Return the structured response
return {
toolName: 'Getting page content',
content: response.text,
dataCollected: true,
};
},
}),
```
## Frontend
Now that the tools and route handler are set up, you can create the frontend.
Use the [useChat](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) hook to create a chat interface.
Here's a simple example of how to use BrowseGPT in a Next.js frontend application:
```typescript theme={null}
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState, useEffect } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const isLoading = status === 'streaming' || status === 'submitted';
const [showAlert, setShowAlert] = useState(false);
const [statusMessage, setStatusMessage] = useState('');
const [sessionId, setSessionId] = useState(null);
useEffect(() => {
if (isLoading) {
setShowAlert(true);
setStatusMessage('The AI is currently processing your request. Please wait.');
setSessionId(null);
} else {
setShowAlert(false);
}
}, [isLoading, messages]);
useEffect(() => {
const lastMessage = messages[messages.length - 1];
if (lastMessage?.parts) {
for (const part of lastMessage.parts) {
if (part.type === 'tool-invocation' && 'result' in part && part.result?.sessionId) {
setSessionId(part.result.sessionId);
break;
}
}
}
}, [messages]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
void sendMessage({ text: input });
setInput('');
};
return (
);
}
```
## Conclusion
You've now seen how to use the Vercel AI SDK to create a chat interface that searches the web using Browserbase.
You can view a demo of this tutorial [here](https://www.browsegpt.dev).
The code for this tutorial is open-sourced [here](https://github.com/browserbase/BrowseGPT).
Demo of BrowseGPT that allows you to search the web using a chat interface.
BrowseGPT is a tool that allows you to search the web using a chat
interface.
# Eve agent integration
Source: https://docs.browserbase.com/integrations/vercel/eve/introduction
Give Eve agents Browserbase Search, Fetch, and persistent cloud browser tools through Stagehand.
[Eve](https://eve.dev/) is a framework for building durable agents. The `@browserbasehq/eve` extension gives an Eve agent browsers that work where APIs can't.
The extension starts with Browserbase Search and Fetch for lightweight discovery and retrieval. When a page requires JavaScript or interaction, your agent can use [Stagehand](/welcome/quickstarts/stagehand) with a persistent Browserbase browser.
## How it works
The extension adds these tools to your Eve agent:
| Tool | What your agent can do |
| ---------------- | ----------------------------------------------------------------------------- |
| `search` | Find relevant public web pages with Browserbase Search. |
| `fetch` | Retrieve raw, Markdown, or structured page content without a browser session. |
| `create_session` | Create or reconnect to a Browserbase browser. |
| `navigate` | Open a URL in the current browser. |
| `observe` | Find elements and candidate actions on the current page. |
| `act` | Perform one natural-language page interaction. |
| `extract` | Return data that matches a JSON Schema. |
| `agent` | Run a multi-step Stagehand task. |
| `stop_session` | Stop the browser session and release its resources. |
Use Search, then Fetch, then a browser as an escalation path. This approach reserves browser sessions for pages that need them.
## Persistent browser sessions
The extension stores the Browserbase session ID in Eve's durable per-session state. Each browser tool reconnects to the same browser, performs its operation, and disconnects without ending the session. This lets your agent keep browser state across Eve workflow steps and Vercel function invocations.
Call `stop_session` when your agent finishes its task to stop billable browser time. Keep-alive availability depends on your Browserbase plan.
## Model access
Stagehand uses [Browserbase Model Gateway](/platform/model-gateway/overview) for browser inference, so one Browserbase API key covers the browser and Stagehand. The outer Eve agent uses Vercel AI Gateway through an `AI_GATEWAY_API_KEY` or a linked Vercel project's `VERCEL_OIDC_TOKEN`.
## Get started
Add the Browserbase extension to an Eve agent and run your first browser
task.
Browse the complete Eve agent example on GitHub.
# Add Browserbase to an Eve agent
Source: https://docs.browserbase.com/integrations/vercel/eve/quickstart
Install the Browserbase Eve extension and run an agent with Search, Fetch, and persistent browser tools.
This guide adds Browserbase to an Eve agent. You'll give the agent Search and Fetch for lightweight web context plus a persistent browser for interactive pages.
Eve requires Node.js 24 or newer. Create a project, then enter its directory:
```bash theme={null}
npx eve@latest init browserbase-eve-agent
cd browserbase-eve-agent
```
Add the Browserbase extension to your project:
```bash theme={null}
pnpm add @browserbasehq/eve
```
Copy your Browserbase API key from the [Dashboard](https://www.browserbase.com/overview). Add it and your Vercel AI Gateway key to `.env`:
```bash .env theme={null}
BROWSERBASE_API_KEY=bb_live_...
AI_GATEWAY_API_KEY=your_vercel_ai_gateway_key
```
The Browserbase API key creates cloud browsers and powers Stagehand through Browserbase Model Gateway. The Vercel AI Gateway key powers the outer Eve agent.
If you link the project to Vercel, you can use `VERCEL_OIDC_TOKEN` instead of `AI_GATEWAY_API_KEY`.
Create `agent/extensions/browserbase.ts`:
```typescript Node.js agent/extensions/browserbase.ts theme={null}
import browserbase from "@browserbasehq/eve";
export default browserbase({
apiKey: process.env.BROWSERBASE_API_KEY!,
model: "openai/gpt-5.4-mini",
});
```
Eve uses the filename as the tool namespace. This file creates tools such as `browserbase__search`, `browserbase__fetch`, `browserbase__navigate`, and `browserbase__extract`.
Create or update `agent/agent.ts`:
```typescript Node.js agent/agent.ts theme={null}
import { defineAgent } from "eve";
export default defineAgent({
model: "openai/gpt-5.4-mini",
});
```
Add these instructions to `agent/instructions.md`:
```markdown theme={null}
Use Browserbase Search to discover sources, then Fetch to retrieve straightforward content.
Create a browser session only when a page requires JavaScript or interaction.
For browser tasks, navigate before you observe, act, or extract.
Stop the browser session when you finish the task.
```
Start Eve's terminal interface:
```bash theme={null}
npx eve dev
```
Try this prompt:
```text theme={null}
Open https://news.ycombinator.com and return the titles and URLs of the first five stories. Use a Browserbase session and stop it when you are done.
```
Open the [Sessions dashboard](https://www.browserbase.com/sessions) to watch the agent use the browser.
## Choose the right browser tool
For predictable runs, use `create_session`, `navigate`, `observe`, and then `act` or `extract`. Use `agent` when the task needs Stagehand to plan several browser steps on its own. Always call `stop_session` after the task.
Compare your project with the complete runnable example.
Review the extension's tools, configuration, and session lifecycle.
# Vercel AI integration
Source: https://docs.browserbase.com/integrations/vercel/introduction
Build AI-powered research agents with parallel browser sessions, deployable on Vercel.
[Vercel](https://www.vercel.com) is the leading platform to develop and deploy front-end applications.
With the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction) and [Stagehand](https://stagehand.dev), you can build AI-powered browser agents that run on Vercel's serverless infrastructure. Stagehand provides a high-level API for browser agents built on top of CDP, with AI-powered extraction and interaction capabilities.
Browserbase provides the cloud browser infrastructure that powers these agents, running headless browsers at scale without you managing infrastructure.
**Looking for native cloud functions?** [Functions](/platform/runtime/overview) provide a purpose-built solution for browser agents that can be invoked via API. No need to manage your own Vercel functions. Perfect for webhooks, scheduled tasks, and dedicated agent workflows.
## Research agent template
The [Browserbase x Next.js template](https://github.com/browserbase/browserbase-nextjs-template) is an AI-powered research agent that runs **parallel browser sessions** to search the web in real-time. It uses Stagehand + Browserbase for browser automation and the Vercel AI SDK with Claude for intelligent extraction and synthesis.
**Key features:**
* **Parallel Browser Sessions**: Multiple browsers run simultaneously, each researching a different source
* **Live Browser Views**: Watch AI agents navigate the web in real-time
* **Multi-Source Research**: Searches DuckDuckGo, Wikipedia, YouTube, Hacker News, and Google News
* **AI-Powered Extraction**: Uses Claude to intelligently extract relevant information from pages
* **Real-time Streaming**: Server-Sent Events deliver results as they're discovered
## Get started
Deploy with one click or follow the quickstart guide to build from scratch.
Step-by-step guide to build a research agent with Stagehand, Browserbase, and the Vercel AI SDK.
One-click deploy the research agent template. The Vercel Marketplace will automatically prompt you to set up Browserbase.
Browse the full source code on GitHub.
Guide to build BrowseGPT to search the web using a chat interface.
# Deploying Puppeteer on Vercel
Source: https://docs.browserbase.com/integrations/vercel/puppeteer
Learn how to set up your project, deploy to Vercel, and scale with Browserbase
This guide walks you through how to build a fully functional backend that can convert any website into HTML, take screenshots, and fill out forms using Puppeteer, Vercel, and Browserbase.
To run these at scale, you'll also use [**headless browsers**](https://docs.browserbase.com/platform/browser/getting-started/what-is-headless-browser) (browsers without a user interface), which are often used for scaling web automations, testing, and data collection.
## Prerequisites
[**Vercel**](https://vercel.com): A developer infrastructure platform that lets you build, deploy, and scale. Vercel owns & maintains Next.js, one of the most popular frontend frameworks, allowing you to build applications completely out-of-box without additional configuration.
[**Browserbase**](https://browserbase.com): A headless browser infrastructure platform that provides ready-to-use browsers out of the box. This includes observability, proxies, Verified, and additional debugging tools for your automation scripts. Browsers are essential for interacting with the web, and Browserbase simplifies this process by managing multiple browser sessions and providing debugging capabilities from the start.
[**Puppeteer**](https://pptr.dev/): A Node library that provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default but can be configured to run a full version of Chrome or Chromium.
## Step 1: Setting up your project
First, you'll need to create a Browserbase account. You can sign up for a free account [here](https://www.browserbase.com/sign-up).
You'll also need a Vercel account, of which you can sign up for a free account [here](https://vercel.com/signup).
Now, create a Next.js app through the CLI. This project is called `vercel-automation`.
```bash theme={null}
npx create-next-app@latest vercel-automation
```
### Install packages
Install Browserbase's Node.js SDK, [Stagehand](https://stagehand.dev) AI SDK, [Zod](https://zod.dev/) for data validation, and [Prettier](https://prettier.io/) for formatting.
```bash theme={null}
npm install @browserbasehq/sdk @browserbasehq/stagehand prettier zod
```
### Managing API keys
Be sure to add environment variables for this project.
You'll need a Browserbase API key. You can get it from the Browserbase Settings.
```bash theme={null}
BROWSERBASE_API_KEY=
```
## Step 2: Using Next.js route handlers
Next.js Route Handlers let you create custom API endpoints that process HTTP requests and return web content through APIs, directly within your application.
* Next.js provides helper classes `NextRequest` and `NextResponse` to simplify working with native [Request](https://developer.mozilla.org/docs/Web/API/Request)/[Response](https://developer.mozilla.org/docs/Web/API/Response) APIs.
* Route Handlers are exclusively available within the `app` directory.
In this project, you'll create three route handlers, each for a different web automation task.
The route handlers use Browserbase headless browser infrastructure for HTML content collection, screenshot captures, and form submissions.
Make sure you have the following directory structure:
```bash theme={null}
vercel-automation/
├── app/
│ ├── api/
│ │ ├── html/
│ │ │ └── route.ts
│ │ ├── screenshot/
│ │ │ └── route.ts
│ │ └── form/
│ │ └── route.ts
```
### HTML
Create the first route handler for retrieving HTML.
Create an `html` directory in the `app/api` folder. To create an endpoint, add a `route.ts` file that handles the API requests. Use the `GET` method to retrieve HTML content from a specified URL.
Here's the code for the first route handler:
```typescript Puppeteer [expandable] theme={null}
// app/api/html/route.ts
import { NextResponse } from "next/server";
import Browserbase from "@browserbasehq/sdk";
import puppeteer from "puppeteer-core";
import prettier from "prettier";
import htmlParser from "prettier/parser-html";
export async function GET(req: Request) {
try {
// Extract URL from request query parameters
const url = new URL(req.url).searchParams.get("url");
if (!url) {
return NextResponse.json({ error: "URL is required" }, { status: 400 });
}
// Initialize Browserbase with API key
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
// Create a new browser session with specified viewport
const session = await bb.sessions.create({
browserSettings: {
viewport: { width: 1920, height: 1080 },
},
});
// Connect to browser instance using Puppeteer
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
// Navigate to URL and capture HTML
const page = await browser.newPage();
await page.goto(url, { waitUntil: "domcontentloaded" });
const html = await page.evaluate(
() => document.querySelector("*")?.outerHTML
);
// Format the HTML
const formattedHtml = await prettier.format(html || "", {
parser: "html",
plugins: [htmlParser],
});
await browser.close();
// Return the HTML
return NextResponse.json({ html: formattedHtml });
} catch (error) {
console.error("HTML generation error:", error);
return NextResponse.json({
error: "Failed to generate HTML",
details: error instanceof Error ? error.message : String(error),
}, { status: 500 });
}
}
```
### Screenshots
For the second route handler, create a new browser session with a specified viewport, navigate to the URL, and screenshot the screen. Create `screenshot/route.ts` and use the following code to enable screenshot abilities.
```tsx Puppeteer [expandable] theme={null}
// app/api/screenshot/route.ts
import { NextResponse } from "next/server";
import Browserbase from "@browserbasehq/sdk";
import puppeteer from "puppeteer-core";
export async function GET(req: Request) {
try {
// Extract URL from request query parameters
const url = new URL(req.url).searchParams.get("url");
if (!url) {
return NextResponse.json({ error: "URL is required" }, { status: 400 });
}
// Initialize Browserbase with API key
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
// Create a new browser session with specified viewport
const session = await bb.sessions.create({
browserSettings: {
viewport: { width: 1920, height: 1080 },
},
});
// Connect to browser instance using Puppeteer
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
// Navigate to URL and capture screenshot
const page = await browser.newPage();
await page.goto(url, { waitUntil: "domcontentloaded" });
const screenshot = await page.screenshot();
await browser.close();
// Set appropriate headers for image response
const headers = new Headers();
headers.set("Content-Type", "image/png");
headers.set("Content-Length", screenshot.byteLength.toString());
// Return screenshot as binary response
return new NextResponse(Buffer.from(screenshot), { status: 200, headers });
} catch (error) {
console.error("Screenshot generation error:", error);
return NextResponse.json(
{
error: "Failed to generate screenshot",
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
}
```
### Form inputs
Puppeteer can be a bit cumbersome for form interactions. For the Form API route handler, the example uses [Stagehand](https://stagehand.dev), an AI SDK.
[Stagehand](https://stagehand.dev) simplifies complex browser interactions by letting you use plain English. Stagehand consists of three main functions: `Act`, `Extract`, and `Observe`.
In this example, Stagehand is initialized with Browserbase credentials and an LLM model to efficiently fill out a sample form, rather than writing a more complex Puppeteer script.
Below is the same web automation task, comparing the Puppeteer and Stagehand implementations.
If you're using Stagehand, you'll need to set up an LLM provider. Be sure to include the environment variable for your LLM provider in your `.env` file.
```tsx Puppeteer [expandable] theme={null}
// app/api/form/route.ts
import { NextResponse } from "next/server";
import Browserbase from "@browserbasehq/sdk";
import puppeteer from "puppeteer-core";
export async function GET(req: Request) {
try {
// Create Browserbase session
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create({
browserSettings: { viewport: { width: 1920, height: 1080 } },
});
// Connect with Puppeteer
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const page = await browser.newPage();
// Navigate to the form
await page.goto(
"https://docs.google.com/forms/d/e/1FAIpQLSdIbWu5keJxnIp4ZGmnGZNlkEd7cYnz_jBRtkE-8xLOoDo5Mw/viewform"
);
await page.waitForSelector("form", { timeout: 10000 });
// Select "Invisibility" radio button
await page.evaluate(() => {
const radio = Array.from(
document.querySelectorAll('[role="radio"]')
).find((el) => el.getAttribute("aria-label") === "Invisibility");
if (radio) (radio as HTMLElement).click();
});
// Select checkboxes for features
const features = ["Verified", "Proxies", "Session Replay"];
for (const feature of features) {
await page.evaluate((featureName) => {
const checkbox = Array.from(
document.querySelectorAll('[role="checkbox"]')
).find((el) => el.getAttribute("aria-label") === featureName);
if (checkbox) (checkbox as HTMLElement).click();
}, feature);
await new Promise((resolve) => setTimeout(resolve, 300));
}
// Fill the text field
const coolestBuild =
"A bot that automates form submissions across multiple sites.";
await page.evaluate((text) => {
// Find the first visible text input
const input = document.querySelector("input.whsOnd.zHQkBf");
if (input) {
(input as HTMLInputElement).value = text;
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
}
}, coolestBuild);
// Submit the form
await new Promise((resolve) => setTimeout(resolve, 300));
await page.evaluate(() => {
const submitButton = Array.from(
document.querySelectorAll('[role="button"]')
).find((el) => el.getAttribute("aria-label") === "Submit");
if (submitButton) (submitButton as HTMLElement).click();
});
// Wait for submission and close browser
await new Promise((resolve) => setTimeout(resolve, 5000));
await browser.close();
// Return success response
return NextResponse.json({
success: true,
sessionUrl: `https://browserbase.com/sessions/${session.id}`,
});
} catch (error) {
console.error("Form submission error:", error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
}
```
```tsx Stagehand [expandable] theme={null}
// app/api/form/route.ts
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
import dotenv from "dotenv";
import { NextResponse } from "next/server";
dotenv.config();
export async function GET(req: Request) {
try {
const stagehand = new Stagehand({
env: "BROWSERBASE",
});
await stagehand.init();
const page = stagehand.context.pages()[0];
const inputs = {
superpower: "Invisibility",
features_used: ["Verified", "Proxies", "Session Replay"],
coolest_build:
"A bot that automates form submissions across multiple sites.",
};
// Navigate to the form
await page.goto("https://forms.gle/f4yNQqZKBFCbCr6j7");
// You can use the observe method to find the selector with an act command to fill it in
const superpowerSelector = await stagehand.observe(
`Find the selector for the superpower field: ${inputs.superpower}`
);
console.log(superpowerSelector);
await stagehand.act(superpowerSelector[0]);
// You can also explicitly specify the action to take
await stagehand.act(
"Select the features used: " + inputs.features_used.join(", ")
);
await stagehand.act(
"Fill in the coolest_build field with the following value: " +
inputs.coolest_build
);
await stagehand.act("Click the submit button");
await page.waitForTimeout(5000);
// Extract to log the status of the form
const status = await stagehand.extract(
"Extract the status of the form",
z.object({ status: z.string() })
);
console.log(status);
await stagehand.close();
return NextResponse.json({
success: true,
formSubmission: {
inputs,
},
});
} catch (error) {
console.error("Form submission error:", error);
}
}
```
### Testing the API endpoints
Now that the three route handlers are implemented in the Next.js app, test the API endpoints.
1. Start the development server:
```bash theme={null}
npm run dev
```
2. Access the API at the base URL:
`http://localhost:3000/api`
3. Test each endpoint by navigating to:
* `http://localhost:3000/api/html`
* `http://localhost:3000/api/screenshot`
* `http://localhost:3000/api/form`
Each endpoint should return a `200` status code when working correctly.
## Step 3: Deploying to Vercel
Finally, after testing the API endpoints locally, deploy to Vercel:
1. Sign in to [Vercel](https://vercel.com)
2. Click **"Add New\..."** → **"Project"**
3. Connect and select your repository
4. Add any environment variables
5. Click **"Deploy"**
6. Once complete, you'll get a deployment URL
### Deploying with fluid compute
[Fluid compute](https://vercel.com/fluid) is a new infrastructure model from Vercel that balances the benefits of dedicated servers and serverless computing. These mini-servers start up only when needed, grow instantly as traffic increases, use what's already running before adding more compute. **You only pay for what you actually use**.
Fluid compute also handles advanced tasks, cuts costs, runs close to your data, requires no setup, and works with standard Node.js and Python. To learn more, you can read more about it in the [announcement](https://vercel.com/blog/introducing-fluid-compute) and [documentation](https://vercel.com/docs/functions/fluid-compute).
### Why Fluid Compute for browser automations
For your route handlers connecting to Browserbase's headless browser infrastructure, Fluid Compute offers key benefits:
* **Performance optimization** - Route handlers that orchestrate complex browser automations remain responsive under load, with warm mini-servers eliminating cold start delays when initiating browser sessions
* **Optimized concurrency** - Multiple function invocations share a single instance, allowing concurrent processing while some requests wait for Browserbase responses, eliminating idle resource waste
* **Extended, efficient runtimes** - Complex automation workflows that would timeout in standard serverless functions complete successfully, while you only pay when your route handlers are processing requests
Although Vercel doesn't handle the browser sessions directly (Browserbase does), Fluid Compute makes browser automation projects significantly more reliable, cost-effective, and performant at scale for AI applications.
### How to enable Fluid Compute
1. Go to your project settings in Vercel
2. Select **Functions** from the left navigation menu
3. Toggle the **Fluid compute** button to enable it
4. Click **Save**
5. Redeploy your project
You will see a higher New Function Duration and New Function Max Duration as a result
Fluid Compute shows how much storage and computing resources you've saved by optimizing resource usage across requests.
As you grow in traffic, multiple requests begin to add up. You can monitor these savings in the [Observability tab](https://vercel.com/docs/observability), which displays metrics on function performance, resource utilization, and cost efficiency.
This data helps you quantify the benefits of Fluid compute as your application scales, potentially reducing your compute costs by up to 85% compared to traditional serverless approaches.
## Conclusion
Congratulations! Now you have a fully functional web application that can convert any website into HTML, take screenshots, and fill out forms using Puppeteer and Browserbase.
This project demonstrates how to leverage Vercel's serverless functions, Next.js route handlers, Fluid Compute, Stagehand, and Browserbase headless browsers to create a practical web application.
Feel free to check out the [completed code on GitHub](https://github.com/browserbase/integrations/tree/master/examples/integrations/vercel/vercel-puppeteer).
# Build a research agent with Stagehand + Vercel
Source: https://docs.browserbase.com/integrations/vercel/quickstart
Build an AI-powered research agent that runs parallel browser sessions on Browserbase, powered by Stagehand and the Vercel AI SDK.
You'll need credentials from two services:
**Browserbase**: Go to the [Dashboard Settings](https://www.browserbase.com/settings) and copy your API key.
**Anthropic**: Get your API key from the [Anthropic Console](https://console.anthropic.com/).
Create a `.env.local` file with:
```bash theme={null}
BROWSERBASE_API_KEY=your_api_key
ANTHROPIC_API_KEY=your_anthropic_key
```
```bash theme={null}
npm i @browserbasehq/stagehand @browserbasehq/sdk ai @ai-sdk/anthropic zod
```
| Package | Purpose |
| -------------------------- | --------------------------------------------- |
| `@browserbasehq/stagehand` | AI-powered browser automation |
| `@browserbasehq/sdk` | Browserbase API client (sessions, live views) |
| `ai` | Vercel AI SDK for structured generation |
| `@ai-sdk/anthropic` | Anthropic model provider |
| `zod` | Schema validation for extracted data |
Initialize a Stagehand instance connected to Browserbase. Each session gets its own cloud browser with a live debug view.
```ts theme={null}
import { Stagehand } from "@browserbasehq/stagehand";
import Browserbase from "@browserbasehq/sdk";
const browserbase = new Browserbase();
async function createStagehandSession(source: string) {
const stagehand = new Stagehand({
env: "BROWSERBASE",
model: "anthropic/claude-sonnet-4-6",
logger: console.log,
disablePino: true,
});
await stagehand.init();
const sessionId = stagehand.browserbaseSessionID!;
const { debuggerFullscreenUrl } = await browserbase.sessions.debug(sessionId);
return { stagehand, sessionId, liveViewUrl: debuggerFullscreenUrl, source };
}
```
Each research function takes a Stagehand instance, navigates to a source, and uses `stagehand.extract()` to pull structured data from the page using AI.
Here's an example that searches DuckDuckGo and visits top results:
```ts theme={null}
import { z } from "zod";
async function researchGoogle(
stagehand: Stagehand,
query: string,
onFinding: (finding: Finding) => void
) {
const page = stagehand.context.activePage()!;
await page.goto(`https://duckduckgo.com/?q=${encodeURIComponent(query)}`);
await page.waitForTimeout(2000);
const searchResults = await stagehand.extract(
"Extract the top 5 organic search result links with their titles and URLs. Skip any ads.",
z.object({
results: z.array(z.object({
title: z.string(),
url: z.string(),
})).max(5),
})
);
for (const result of searchResults.results.slice(0, 3)) {
if (!result.url || result.url.includes("duckduckgo.com")) continue;
await page.goto(result.url, { waitUntil: "domcontentloaded", timeoutMs: 15000 });
const content = await stagehand.extract(
`Extract the key information about "${query}" from this article.`,
z.object({
summary: z.string(),
keyFacts: z.array(z.string()),
})
);
if (content.summary) {
onFinding({
title: result.title,
source: new URL(result.url).hostname.replace("www.", ""),
url: result.url,
summary: content.summary,
relevance: "high",
});
}
}
}
```
You can create similar functions for Wikipedia, YouTube, Hacker News, and Google News, each using `stagehand.extract()` with different schemas. See the [full template](https://github.com/browserbase/browserbase-nextjs-template/blob/main/app/api/research/route.ts) for all five research functions.
Create `app/api/research/route.ts` to handle research requests. This route creates parallel Stagehand sessions and streams findings back via Server-Sent Events.
```ts theme={null}
import { generateObject } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export const maxDuration = 300;
const ResearchSummarySchema = z.object({
overview: z.string().describe("2-3 sentence direct answer to the query"),
keyFacts: z.array(z.string()).describe("3-6 specific facts with dates, numbers, or names"),
recentDevelopments: z.string().nullable().describe("Latest news if applicable"),
sourcesSummary: z.string().describe("Brief note on the types of sources consulted"),
});
export async function POST(req: Request) {
const { query } = await req.json();
const encoder = new TextEncoder();
const stream = new TransformStream();
const writer = stream.writable.getWriter();
const sendEvent = async (event: string, data: unknown) => {
await writer.write(
encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
);
};
(async () => {
const sessions = [];
const allFindings: Finding[] = [];
const researchFunctions = [
{ source: "News", fn: researchGoogleNews },
{ source: "Hacker News", fn: researchHackerNews },
{ source: "YouTube", fn: researchYouTube },
{ source: "Wikipedia", fn: researchWikipedia },
{ source: "Search", fn: researchGoogle },
];
try {
await sendEvent("status", { message: "Starting browser sessions...", phase: "init" });
// Create all Stagehand sessions in parallel
const sessionPromises = researchFunctions.map(({ source }) =>
createStagehandSession(source)
);
const createdSessions = await Promise.all(sessionPromises);
sessions.push(...createdSessions);
// Send live view URLs to frontend
await sendEvent("liveViews", {
sessions: sessions.map(s => ({
source: s.source,
liveViewUrl: s.liveViewUrl,
sessionId: s.sessionId,
})),
});
// Run all research in parallel
await Promise.allSettled(
researchFunctions.map(({ source, fn }, index) =>
fn(
sessions[index].stagehand,
query,
(finding) => {
allFindings.push(finding);
sendEvent("findings", { findings: allFindings });
}
)
)
);
// Synthesize findings with AI
if (allFindings.length > 0) {
const findingsText = allFindings
.map((f) => `Source: ${f.source}\n${f.summary}`)
.join("\n\n---\n\n");
const { object: summary } = await generateObject({
model: anthropic("claude-sonnet-4-6"),
schema: ResearchSummarySchema,
prompt: `Based on these research findings about "${query}", create a structured summary.\n\n${findingsText}`,
});
await sendEvent("complete", { findings: allFindings, summary });
}
} finally {
for (const session of sessions) {
try { await session.stagehand.close(); } catch {}
}
await writer.close();
}
})();
return new Response(stream.readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
```
Free Browserbase plans have a concurrency limit of 1. The template automatically detects this and falls back to running sessions sequentially:
```ts theme={null}
async function getProjectConcurrency(): Promise {
const projects = await browserbase.projects.list();
if (!projects?.length) return 1;
const project = await browserbase.projects.retrieve(projects[0].id);
return project.concurrency ?? 1;
}
// In your POST handler:
const concurrency = await getProjectConcurrency();
if (concurrency === 1) {
// Run browsers one at a time, closing each before starting the next
for (const { source, fn } of researchFunctions) {
const session = await createStagehandSession(source);
await fn(session.stagehand, query, onFinding);
await session.stagehand.close();
}
} else {
// Run all browsers in parallel
const sessions = await Promise.all(
researchFunctions.map(({ source }) => createStagehandSession(source))
);
await Promise.allSettled(
researchFunctions.map(({ fn }, i) =>
fn(sessions[i].stagehand, query, onFinding)
)
);
}
```
Congratulations! You've built an AI research agent that runs parallel browser sessions with Stagehand and Browserbase on Vercel.
For the complete implementation including the frontend UI with live browser views, check out the full template:
Browse the complete source code with frontend components, SSE streaming, and live browser views.
One-click deploy with automatic Browserbase setup via the Vercel Marketplace.
# Human verification with AgentKit
Source: https://docs.browserbase.com/integrations/x402/agentkit
Prove your agent is human-backed to unlock Verified browsers on x402 sessions.
When you pay for a browser session via x402, you get a standard browser by default. To unlock [Verified browsers](/platform/identity/verified-customization), purpose-built Chromium recognized by bot protection partners, your agent needs to prove it's operating on behalf of a real human.
This is where [AgentKit](https://docs.world.org/agents/agent-kit) comes in. AgentKit provides cryptographic proof-of-humanity using the [World Chain](https://world.org/world-chain) network, so your agent can prove it's human-backed without revealing who that human is.
AgentKit human verification is only required for x402 sessions. If you're using the standard Browserbase SDK with API keys, verified browsers are available directly through your plan.
## How it works
Register your EVM wallet address in the [AgentBook](https://docs.world.org/agents/agent-kit) smart contract on World Chain. This is a one-time setup that links your wallet to a verified human identity.
Before each session request, your agent signs an [EIP-191](https://eips.ethereum.org/EIPS/eip-191) message proving it controls a registered wallet. The signed message includes the target URL, a nonce, and a short TTL for replay protection.
Include the signed proof as an `agentkit` HTTP header alongside your `X-PAYMENT` header. The gateway verifies the signature, checks AgentBook registration, and upgrades your session.
If verification succeeds, your session gets Browserbase's [Verified](/platform/identity/verified-customization) browser, purpose-built Chromium recognized by bot protection partners and the same premium feature available to Scale plan customers.
## Session behavior
| Scenario | Browser |
| ----------------------------------------------- | ------------ |
| x402 payment, no `agentkit` header | Standard |
| x402 payment, invalid or expired proof | Standard |
| x402 payment, valid proof from registered human | **Verified** |
The gateway never returns an error for missing or invalid proofs, it silently falls back to a standard browser. Your agent always gets a working session.
## Setup
### 1. Install dependencies
```bash theme={null}
npm install @worldcoin/agentkit viem
```
### 2. Register in AgentBook
You need a wallet registered in [AgentBook](https://docs.world.org/agents/agent-kit) on World Chain. Registration requires the wallet holder to be a verified human (Orb-verified via World App).
```bash theme={null}
npx @worldcoin/agentkit-cli register
```
Registration is a one-time on-chain transaction on World Chain (chain ID `eip155:480`). Once registered, any agent signing with this wallet's private key can prove human backing.
### 3. Sign the AgentKit header
Your agent signs a [SIWE](https://eips.ethereum.org/EIPS/eip-4361)-formatted message before each request:
```typescript theme={null}
import { privateKeyToAccount } from "viem/accounts";
import { formatSIWEMessage } from "@worldcoin/agentkit";
import { randomBytes } from "crypto";
const account = privateKeyToAccount(process.env.AGENTKIT_PRIVATE_KEY as `0x${string}`);
async function signAgentkitHeader(url: string): Promise {
const parsed = new URL(url);
const now = new Date();
const expiry = new Date(now.getTime() + 5 * 60 * 1000); // 5-minute TTL
const info = {
domain: parsed.host,
uri: url,
version: "1" as const,
nonce: randomBytes(16).toString("hex"),
issuedAt: now.toISOString(),
expirationTime: expiry.toISOString(),
chainId: "eip155:480", // World Chain
type: "eip191" as const,
statement: "Verify your agent is backed by a real human",
};
const message = formatSIWEMessage(info, account.address);
const signature = await account.signMessage({ message });
const payload = { ...info, address: account.address, signature };
return Buffer.from(JSON.stringify(payload)).toString("base64");
}
```
### 4. Create a verified x402 session
Combine the `agentkit` header with your x402 payment:
```typescript theme={null}
import { wrapFetchWithPayment } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";
const walletAccount = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const x402Fetch = wrapFetchWithPayment(fetch, walletAccount);
const url = "https://x402.browserbase.com/browser/session/create";
// Sign the AgentKit proof
const agentkitHeader = await signAgentkitHeader(url);
// Create session with both payment and human verification
const session = await x402Fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"agentkit": agentkitHeader,
},
body: JSON.stringify({ estimatedMinutes: 30 }),
}).then((r) => r.json());
console.log(session.connectUrl); // Verified browser session
```
The same wallet can be used for both x402 payments (USDC on Base) and AgentKit signing (identity on World Chain). They serve different purposes (payment vs. identity) but can share a key pair for convenience.
## Server-side verification
The x402 gateway verifies AgentKit proofs in four steps:
1. **Decode**: Base64-decode the `agentkit` header and parse the JSON payload
2. **Validate**: Check the SIWE message fields, TTL (must not be expired), and target URI
3. **Recover**: ECRECOVER the signer's address from the EIP-191 signature
4. **Lookup**: Query the AgentBook contract on World Chain (`eip155:480`) to confirm the address belongs to a registered human
If any step fails, the gateway logs the reason and falls back to a standard browser. No error is returned to the client.
## FAQ
Yes. AgentBook registration requires the wallet holder to be verified through World App's Orb verification.
Yes. The x402 payment wallet (USDC on Base) and the AgentKit signing wallet (registered on World Chain) are independent. You can use the same wallet for both or separate them.
Nothing. The AgentKit proof is only checked at session creation time. Once your session is created as Verified, it stays that way for the session's lifetime.
No. The AgentKit proof is between your agent and the x402 gateway. Websites see a Verified Browserbase browser, they don't see your wallet address or World ID.
You still get a working browser session (you paid for it via x402). It just won't have Verified browser fingerprints. The gateway never rejects a paid request over a missing proof.
## Further reading
Set up x402 payments for browser sessions
AgentKit SDK documentation
How Browserbase handles agent authentication
# x402 protocol integration
Source: https://docs.browserbase.com/integrations/x402/introduction
Pay-per-use browser sessions with crypto. No API keys, no accounts. Just pay and connect.
The **x402 integration** enables pay-per-use browser sessions using cryptocurrency. Built on the [x402 protocol](https://x402.org), this integration lets you create and manage Browserbase sessions by paying with USDC on Base, with no API keys or accounts required.
This integration uses the HTTP 402 Payment Required status code to enable seamless crypto payments for browser automation.
**Endpoint:** `https://x402.browserbase.com`
## Key features
Start using browser sessions immediately without signing up or managing API keys
Only pay for what you use with transparent per-minute pricing
Pay with USDC on Base network using any EVM-compatible wallet
Receive WebSocket connection URLs immediately after payment
## How it works
Make a request to the x402 endpoint. The server returns payment requirements with a 402 response.
Your wallet signs a USDC authorization for the requested amount.
Send the signed payment header and receive your browser WebSocket URL instantly.
## Pricing
| Duration | Cost (USDC) |
| -------- | ----------- |
| 5 min | \$0.01 |
| 15 min | \$0.03 |
| 30 min | \$0.06 |
| 60 min | \$0.12 |
**Rate:** \$0.12/hour
## Requirements
Base (mainnet)
USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`)
Any EVM wallet with USDC on Base
## Use cases
Test browser automation without commitment or account setup
Run browser sessions without creating an account or sharing personal information
Integrate browser automation into Web3 applications with native payment flows
Ideal for irregular usage patterns where subscription plans don't make sense
## Premium features with AgentKit
By default, x402 sessions use a standard browser. To unlock **Verified browsers**, the same premium feature available to Scale plan customers, your agent can prove it's human-backed using [AgentKit](/integrations/agentkit/introduction) from Tools for Humanity.
Pay with USDC, get a working browser. No account or identity required.
Add an AgentKit proof header to unlock Verified browsers.
This creates a layered access model. Anyone can pay for a browser, but premium features are reserved for agents that can cryptographically prove human backing. No API keys, no KYC. Just math.
Learn how to prove your agent is human-backed and unlock premium features
## Getting started
Ready to start using pay-per-use browser sessions? Check out the quickstart guide to create your first session.
Create your first x402 browser session
Learn more about the x402 payment protocol
# x402 quickstart
Source: https://docs.browserbase.com/integrations/x402/quickstart
Create pay-per-use browser sessions with crypto payments
## Quick start
```bash theme={null}
npm install viem x402
```
Run the following with your wallet's private key (requires USDC on Base):
```bash theme={null}
PRIVATE_KEY=0x... npx tsx scripts/test-with-wallet.ts
```
## API reference
### Create session
Creates a new browser session with prepaid time.
```
POST /browser/session/create
```
**Request Body:**
```json theme={null}
{
"estimatedMinutes": 30
}
```
When you first make a request, you'll receive a 402 Payment Required response with payment details:
```json theme={null}
{
"x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "base",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxAmountRequired": "500000",
"payTo": "0x...",
"resource": "https://x402.browserbase.com/browser/session/create"
}]
}
```
After sending the payment header, you'll receive your session details:
```json theme={null}
{
"sessionId": "abc-123",
"connectUrl": "wss://connect.browserbase.com/...",
"paidMinutes": 30,
"expiresAt": "2025-12-03T18:00:00Z",
"pricing": {
"ratePerHour": 0.12,
"amountPaid": 0.06,
"currency": "USDC"
}
}
```
### Get session status
Check the status of an active session.
```
GET /browser/session/:id/status
```
**Response:**
```json theme={null}
{
"sessionId": "abc-123",
"status": "active",
"usage": {
"minutesPaid": 30,
"minutesUsed": 5,
"minutesRemaining": 25
},
"expiresAt": "2025-12-03T18:00:00Z"
}
```
### Extend session
Add more time to an active session.
```
POST /browser/session/:id/extend
```
**Request Body:**
```json theme={null}
{
"additionalMinutes": 15
}
```
Extending a session requires another x402 payment for the additional time.
### Terminate session
End a session early. Unused time may be eligible for refund.
```
POST /browser/session/:id/terminate
```
**Response:**
```json theme={null}
{
"sessionId": "abc-123",
"finalStatus": "terminated",
"usage": {
"minutesPaid": 30,
"minutesUsed": 10
},
"refund": {
"eligible": true,
"amount": 0.33,
"currency": "USDC"
}
}
```
## Making x402 payments
Use the x402 package for full control over the payment flow:
```typescript theme={null}
import { privateKeyToAccount } from "viem/accounts";
import { getAddress } from "viem";
import { exact } from "x402/schemes";
const account = privateKeyToAccount("0x...");
// 1. Get payment requirements
const res = await fetch("https://x402.browserbase.com/browser/session/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ estimatedMinutes: 30 }),
});
const { accepts } = await res.json();
const req = accepts[0];
// 2. Create payment header
const paymentHeader = await exact.evm.createPaymentHeader(account, 1, {
scheme: req.scheme,
network: req.network,
maxAmountRequired: req.maxAmountRequired,
resource: req.resource,
description: req.description,
mimeType: req.mimeType,
payTo: getAddress(req.payTo),
maxTimeoutSeconds: req.maxTimeoutSeconds,
asset: getAddress(req.asset),
extra: req.extra,
});
// 3. Send with payment
const session = await fetch("https://x402.browserbase.com/browser/session/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-PAYMENT": paymentHeader,
},
body: JSON.stringify({ estimatedMinutes: 30 }),
}).then(r => r.json());
console.log(session.connectUrl); // wss://connect.browserbase.com/...
```
Use `x402-fetch` for automatic payment handling:
```typescript theme={null}
import { wrapFetch } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount("0x...");
const x402Fetch = wrapFetch(fetch, account);
// Payments are handled automatically
const session = await x402Fetch("https://x402.browserbase.com/browser/session/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ estimatedMinutes: 30 }),
}).then(r => r.json());
console.log(session.connectUrl); // wss://connect.browserbase.com/...
```
The `x402-fetch` wrapper automatically handles the 402 response, signs the payment, and retries the request with the payment header.
## Using your session
Once you have a `connectUrl`, connect with Playwright or Puppeteer:
```typescript Playwright theme={null}
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0].pages()[0];
await page.goto("https://example.com");
await page.screenshot({ path: "screenshot.png" });
await browser.close();
```
```typescript Puppeteer theme={null}
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const page = (await browser.pages())[0];
await page.goto("https://example.com");
await page.screenshot({ path: "screenshot.png" });
await browser.close();
```
## Further reading
Learn about the x402 payment protocol
Bridge or swap tokens to get USDC on Base
Explore more Browserbase features
# Concurrency management
Source: https://docs.browserbase.com/optimizations/concurrency/overview
Session limits and rate controls for concurrent browsers
## Key limits
Running multiple browser sessions simultaneously unlocks powerful automation. Whether you're collecting data at scale, running parallel tests, or serving multiple users, you'll need to understand concurrency and rate limits.
To ensure system stability and fair resource allocation, two key limits apply:
* **Max Concurrent Browsers**: The maximum number of browser sessions that you can run at the same time
* **Session Creation Limit**: The maximum number of new browser sessions you can create within any 60-second period
If either limit is reached, your request will receive a 429 (too many requests) error.
**One Minute Minimum:** each browser session requires dedicated resources and
has a minimum runtime of one minute, even if closed before.
## Limits by plan
These limits depend on your plan:
Plan
Free
Developer
Startup
Scale
Max Concurrent Browsers
3
25
100
250+
Session Creation Limit per minute
5
25
50
150+
## Limits and concurrency per project
Browserbase assigns concurrency at the organization level. If you're on the Developer plan, you have 25 total concurrent browsers allotted to your organization, distributed across your projects as you see fit.
With one project, all concurrent browsers simply go to that one project. When you create a second project, 1 concurrent browser is automatically added to your second project (since you need at least one browser per project). This subtracts from your first project.
If you have two projects, here's how the concurrency will assign by default:
* **Developer plan**: Project 1 (24 browsers) + Project 2 (1 browser)
* **Startup plan**: Project 1 (99 browsers) + Project 2 (1 browser)
* **Scale plan**: Fully custom
### Adjust concurrency
You can adjust the concurrency for your projects in the dashboard. Go to the settings page, then under organization click on projects. Then click on the triple dots next to the project you want to adjust and select "Update concurrency". Then you can adjust the concurrency for each project.
## Using your concurrency
Once you know your concurrency limits, you can put them to work. Run multiple browser sessions in parallel to speed up processing a list of URLs, running tests across pages, or powering multi-agent workflows.
### Parallel sessions
You can use `Promise` (TypeScript) or `asyncio` (Python) to run your browser sessions in parallel. The example below extracts article headlines from multiple pages of Hacker News concurrently. Each page gets its own browser session:
```typescript theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const pages = [1, 2, 3];
const urls = pages.map((p) => `https://news.ycombinator.com/?p=${p}`);
async function getHeadlines(url: string) {
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
try {
const page = browser.contexts()[0].pages()[0];
await page.goto(url, { waitUntil: "domcontentloaded" });
const headlines = await page.$$eval(".titleline > a", (links) =>
links.map((a) => ({ title: a.textContent, href: a.href }))
);
return { url, headlines };
} finally {
await browser.close();
}
}
// Run all 3 pages concurrently
const results = await Promise.allSettled(urls.map(getHeadlines));
for (const result of results) {
if (result.status === "fulfilled") {
console.log(`\n--- ${result.value.url} ---`);
result.value.headlines.forEach((h, i) => console.log(`${i + 1}. ${h.title}`));
} else {
console.error("Session failed:", result.reason);
}
}
```
```python theme={null}
import asyncio
import os
from playwright.async_api import async_playwright
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
pages = [1, 2, 3]
urls = [f"https://news.ycombinator.com/?p={p}" for p in pages]
async def get_headlines(url: str, pw):
session = bb.sessions.create()
browser = await pw.chromium.connect_over_cdp(session.connect_url)
try:
page = browser.contexts[0].pages[0]
await page.goto(url, wait_until="domcontentloaded")
links = await page.query_selector_all(".titleline > a")
headlines = []
for link in links:
title = await link.text_content()
href = await link.get_attribute("href")
headlines.append({"title": title, "href": href})
return {"url": url, "headlines": headlines}
finally:
await browser.close()
async def main():
async with async_playwright() as pw:
results = await asyncio.gather(
*(get_headlines(url, pw) for url in urls),
return_exceptions=True,
)
for result in results:
if isinstance(result, Exception):
print(f"Session failed: {result}")
else:
print(f"\n--- {result['url']} ---")
for i, h in enumerate(result["headlines"], 1):
print(f"{i}. {h['title']}")
asyncio.run(main())
```
## Reaching limits: 429s
When reaching the session concurrency limit of your plan, any subsequent request to create a new session will return an HTTP `429 Too Many Requests` error. That means the request was effectively dropped.
For example, if you have a Developer plan (with a limit of 25 concurrent sessions) you can create up to 25 sessions in a 60 second window. If you try to create a 26th session within that window, it will be rate limited and return an HTTP 429 error.
To check the status of your rate limit, you can look at the headers of the response:
* `x-ratelimit-limit` - How many requests you can make.
* `x-ratelimit-remaining` - How many requests remain in the time window.
* `x-ratelimit-reset` - How many seconds must pass before the rate limit resets.
* `retry-after` - If the max has been reached, this is the number of seconds you must wait before you can make another request. This is documented [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After).
```text theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
x-ratelimit-limit: 25
x-ratelimit-remaining: 0
x-ratelimit-reset: 45
retry-after: 45
```
## Handling rate limits
If you're attempting to run more sessions than your current project's concurrency limit, some session creation requests will return a `429`. One option is to wrap session creation in a retry helper that respects the `retry-after` header:
```typescript theme={null}
const MAX_RETRIES = 5;
async function createSessionWithRetry() {
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
return await bb.sessions.create();
} catch (error: any) {
if (error.status !== 429 || attempt === MAX_RETRIES - 1) throw error;
const retryAfter = parseInt(error.headers?.["retry-after"] ?? "2", 10);
const backoff = retryAfter * 1000 * Math.pow(2, attempt);
await new Promise((resolve) => setTimeout(resolve, backoff));
}
}
}
```
```python theme={null}
MAX_RETRIES = 5
async def create_session_with_retry():
for attempt in range(MAX_RETRIES):
try:
return bb.sessions.create()
except Exception as error:
status = getattr(error, "status_code", None)
if status != 429 or attempt == MAX_RETRIES - 1:
raise
retry_after = int(getattr(error, "headers", {}).get("retry-after", 2))
backoff = retry_after * (2 ** attempt)
await asyncio.sleep(backoff)
```
**Serverless Alternative:** [Functions](/platform/runtime/overview) provide built-in concurrency management and automatic session lifecycle. Functions handle session creation and cleanup automatically, making them ideal for high-concurrency workloads without manual session management.
If you need more concurrency, upgrade to a plan with higher limits. See [Plans](/account/billing/plans) for more details, or reach out to Browserbase at [support@browserbase.com](mailto:support@browserbase.com) with any questions.
# Cost optimization
Source: https://docs.browserbase.com/optimizations/cost/cost-optimization
Your guide to lowering your browser infrastructure costs
This guide outlines best practices to minimize infrastructure costs while maintaining performance.
## Session management
### Reusing sessions
To optimize costs, consider reusing browser sessions since there's a one-minute minimum billing period for each session creation. For short tasks, reusing sessions helps avoid multiple minimum charges. For longer workflows, you can disconnect and reconnect to the same session as needed, maintaining efficiency while managing resource usage.
To reuse a session:
* Store the session ID from your initial session creation
* Use the `sessionId` query parameter when connecting to specify the existing session
* Continue using the same session for similar workloads
Learn more about [connecting to sessions here](/platform/browser/getting-started/using-browser-session).
### Serverless execution model
For short-lived, event-driven automations, [Functions](/platform/runtime/overview) provide a cost-effective alternative to manually managing sessions:
* **No minimum runtime waste**: Functions only bill for actual execution time
* **Zero infrastructure overhead**: No session management or keep-alive costs
* **Automatic cleanup**: Sessions are automatically terminated after function completion
* **Perfect for**: Webhooks, scheduled tasks, API endpoints, and one-off automation
Functions are ideal when you don't need persistent sessions or manual control over the session lifecycle.
## Proxy optimization
Proxy usage can impact costs. Implement these strategies to minimize proxy-related expenses:
### Selective proxy usage
Proxies are a powerful tool if you need to access geo-restricted content, have load balancing requirements, or need anonymity, but if those aren't necessary, avoiding proxies saves on costs.
You can also implement domain-specific proxy routing. For more information, see [Proxy Configuration](/platform/identity/proxies#proxies-routing-rules).
### Image loading optimization
When using proxies, control image loading to reduce bandwidth costs.
Ensure images are disabled for non-visual automation:
```typescript Playwright theme={null}
await page.route('**/*', (route, request) => {
if (request.resourceType() === 'image') return route.abort();
return route.continue();
});
await page.goto('https://example.com');
```
```typescript Puppeteer theme={null}
await page.setRequestInterception(true);
page.on('request', (request) => {
if (request.resourceType() === 'image') request.abort();
else request.continue();
});
await page.goto('https://example.com');
```
```python Playwright theme={null}
def block_images(route, request):
if request.resource_type == "image":
route.abort()
else:
route.continue_()
page.route("**/*", block_images)
page.goto("https://example.com")
```
Blocking images or other resources via request interception (`page.route`) is not recommended on sites with bot protection. Many bot protection systems rely on image and font loading to verify browser behavior, and intercepting requests can negatively impact session performance. Reserve this optimization for sites where bot protection is not present.
If you're scaling up and looking for bulk usage discounts, reach out to [hello@browserbase.com](mailto:hello@browserbase.com).
# Usage tracking
Source: https://docs.browserbase.com/optimizations/cost/measuring-usage
Learn how to measure your usage of Browserbase
Track your proxy and session usage to optimize costs and manage quotas. Browserbase gives you a dashboard overview, the Project Usage API, and session metadata for granular insights.
## Browserbase dashboard
The Browserbase Analytics Dashboard provides a real-time view of your session activity, browser usage, and proxy data consumption. It helps you monitor performance, track trends, and optimize automation workflows at a glance.
View your usage in the [Browserbase Dashboard](https://www.browserbase.com/overview).
#### Features
* **Customizable time range:** View data for the last 24 hours, 7 days, 30 days, or your billing cycle to analyze trends over different periods.
* **Real-time refresh:** Update your analytics with the latest data instantly.
* **Detailed metrics on hover:** Hover over charts to see precise session, proxy, and usage details.
#### Metrics
* **Total sessions and browser minutes:** Track session volume and total runtime.
* **Avg. session duration and proxy data:** Monitor efficiency and bandwidth usage.
* **Session status breakdown:** Identify errors, timeouts, and completion rates.
## Using the project usage API
For programmatic tracking, the Project Usage API lets you retrieve detailed usage data through an API request. This is useful for:
* Automating usage reports to monitor trends over time.
* Setting up alerts when usage approaches a predefined limit.
* Integrating with external billing or monitoring tools for cost control.
To access the [Project Usage API](/reference/api/get-project-usage), you can use the following endpoint:
```typescript SDK theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const projectId = process.env.BROWSERBASE_PROJECT_ID!;
const usage = await bb.projects.usage(projectId);
console.log(usage);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
project_id = os.environ["BROWSERBASE_PROJECT_ID"]
usage = bb.projects.usage(project_id)
print(usage)
```
**Tracking Function Invocations:** [Functions](/platform/runtime/overview) automatically create sessions that appear in your usage metrics. Use session metadata in your function's `sessionConfig` to segment and track function invocations alongside regular sessions.
## Segmenting usage with metadata
To segment usage by metadata, you can add metadata to your sessions. This metadata is stored in the session metadata field and can be used to segment usage in the Browserbase Dashboard. Find more information in the [Session Metadata](/platform/browser/core-features/session-metadata) documentation.
1. Create a session with metadata
2. Query the session usage data with the metadata
### Create a session with metadata
Session metadata lets you tag and categorize sessions for better tracking, segmentation, and analysis. By adding metadata, you can:
* Filter and analyze usage by project, client, or workflow.
* Track performance and debugging data across different runs.
* Segment sessions based on region, proxy settings, or automation type.
To create a session with metadata, include a userMetadata object when making a session request. You can later retrieve, filter, and use this metadata for insights and optimizations.
Example use cases:
* Labeling sessions by client ("client": "enterprise\_customer\_xyz")
* Tagging workflow type ("task": "checkout\_automation")
* Storing region or proxy details ("region": "us-west")
* Debugging with test IDs ("test\_id": "A/B-variant-3")
```typescript SDK theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env["BROWSERBASE_API_KEY"]! });
async function createSessionWithMetadata() {
const session = await bb.sessions.create({
userMetadata: {
"key": "value",
"key2": {
"key2A": "value2A",
"key2B": "value2B"
}
},
});
return session;
}
const session = await createSessionWithMetadata();
console.log("Session URL: https://browserbase.com/sessions/" + session.id);
```
```python API theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
def create_session_with_metadata():
session = bb.sessions.create(
user_metadata={
"client": "enterprise_customer_xyz"
}
)
print(f"Session URL: https://browserbase.com/sessions/{session.id}")
return session
session = create_session_with_metadata()
```
### Query the session usage data with the metadata
You can filter and analyze session usage by querying sessions based on their metadata. This lets you track specific workflows, clients, or regions to gain insights into usage patterns, optimize automation, and manage costs effectively.
By using metadata filters, you can:
* Retrieve sessions by client, project, or task type to analyze usage trends.
* Segment usage based on region, proxy settings, or session status.
* Identify high-bandwidth sessions by tagging and filtering resource-heavy processes.
To query session data, use the q parameter in the API request with a metadata filter expression to retrieve sessions matching specific criteria, such as completed sessions for a particular client or region.
Format the query correctly:
```
query = "user_metadata['key']:'value'"
```
You need to URL encode the query string to ensure it's properly parsed by the API.
* `%5B` is the URL encoded version of `[`
* `%5D` is the URL encoded version of `]`
* `%3A` is the URL encoded version of `:`
In JavaScript, you can use `encodeURIComponent("user_metadata['order']['status']:'shipped'")` to encode the query string.
```typescript API theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({apiKey: process.env["BROWSERBASE_API_KEY"]!});
async function listSessionsWithMetadata(query: string) {
const sessions = await bb.sessions.list({
q: query
});
return sessions;
}
const query = "user_metadata['client']:'enterprise_customer_xyz'";
const sessions = await listSessionsWithMetadata(query);
console.log(sessions);
```
```python API theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
def list_sessions_with_metadata(query: str) -> dict:
"""
Retrieve sessions filtered by metadata query.
Args:
query (str): Metadata filter expression
Returns:
dict: List of matching sessions
"""
sessions = bb.sessions.list(
q=query
)
return sessions
# Example usage
query = "user_metadata['client']:'enterprise_customer_xyz'"
sessions = list_sessions_with_metadata(query)
print(sessions)
```
# Browser regions
Source: https://docs.browserbase.com/optimizations/latency/multi-region
Connect to a browser closest to you for improved performance.
Browserbase supports running browsers in multiple geographic regions. Running
browser sessions in or near your region significantly improves performance.
Explicit session creation decouples session creation from connection, supporting use cases like [parallelizing jobs](/optimizations/latency/speed-optimization#implement-parallel-processing) or [long-running sessions](/platform/browser/long-sessions/overview), and lets you customize session settings.
You can specify a browser region using the `region` parameter in the [Sessions API](/reference/api/create-a-session).
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create({
region: "ap-southeast-1",
});
```
```python theme={null}
import os
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
region="ap-southeast-1",
)
```
Available regions: `us-west-2` (Oregon), `us-east-1` (Virginia),
`eu-central-1` (Frankfurt), and `ap-southeast-1` (Singapore). All sessions are
created in `us-west-2` by default.
# Performance optimization
Source: https://docs.browserbase.com/optimizations/latency/speed-optimization
Best practices for speeding up your browsers
You'll find these best practices for optimizing performance throughout the docs, but they're combined here for reference.
The optimizations fall into two sections:
1. **Architecture and implementation optimizations**
2. **Browser session configuration and optimizations**
## Architectural performance patterns
### Decrease round trip time (RTT) by running browsers in your region
When prototyping locally, the round trip latency of the request minus execution time (the round trip time) is much shorter than when hosted. This is especially true when factoring in nonlocal capabilities like CAPTCHA solving or proxies.
Keep in mind that each interaction with the page, even ones that seem atomic, can result in several underlying CDP commands, increasing the RTT.
Using browser sessions geographically close to where you deploy your code reduces round trip time. Region localizing may produce an 8-9x gain in performance without code changes.
To learn more about running region-localized sessions, check out the
[multi-region guide](/optimizations/latency/multi-region).
### Implement parallel processing
When using Browserbase to run jobs, increase speeds by parallelizing your job processing. If you process the work concurrently, tasks can run in parallel, completing faster.
Both Node.js and Python have support for parallel processing.
Here's a Python example to illustrate:
```python Python theme={null}
# SLOWER -- processes elements sequentially
# for an_html_element in html_elements:
# await get_element_text(an_html_element)
# FASTER -- processes elements in parallel
await asyncio.gather(
*(get_element_text(an_html_element) for an_html_element
in html_elements)
)
```
### Parallelize session creation with app initialization
If your app has setup steps before processing work, parallelize session creation with that work. During initialization, use the [Sessions API](/reference/api/create-a-session). This effectively eliminates some or all of the perceived clock time for creating the new session.
**Serverless Scaling:** [Functions](/platform/runtime/overview) handle infrastructure scaling and session management automatically, making them ideal for event-driven automation workloads.
### Choose the right runtime environment
Try comparing performance across implementation languages and runtimes.
You may discover that Node.js outperforms Python in raw speed for I/O-bound requests, whereas Python may be a better choice for CPU-bound tasks.
### Follow your framework's best practices
## Browser session configuration and optimizations
### Avoid multi-tab browser sessions
Browserbase supports browser sessions with multiple tabs, but depending on the workload, you'll likely notice worse performance with more tabs. You'll get better performance by using multiple single-tab browser sessions instead.
### Optimize page loading with `waitUntil` option
You can optimize page loading by configuring the `waitUntil` option when navigating to a page. Using the `domcontentloaded` event instead of the default `load` setting can significantly improve load speed:
```javascript Node.js theme={null}
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
```
This instructs the browser to mark the page as "loaded" once the initial HTML document is fully parsed, without waiting for external resources like stylesheets, images, and frames. This approach can significantly reduce perceived load times, particularly for content-heavy pages.
# Examples
Source: https://docs.browserbase.com/platform/agents/examples
Ready-to-use prompts and structured output for deep research, monitoring, and high-scale automation with Agents.
Each example below is a complete starting point: a **system prompt** that defines the Agent's job, a **structured output** JSON Schema, and the **task** you send to run it. The system prompt is written so the Agent reads its target (a company, a URL) straight from the task, so you can run it by changing the task alone.
Structured output is a JSON Schema you set on the Agent. Keep it flat with scalar fields (`string`, `number`, `boolean`) so the output is quick to read back and diff. Build the Agent once in the [dashboard](/welcome/quickstarts/agents), then trigger runs with [Run an Agent](/reference/api/run-an-agent).
## Deep research: KYC and KYB checks
Know Your Customer (KYC) and Know Your Business (KYB) checks combine discovery and document retrieval across sources that have no API: company registries, sanctions lists, regulator portals, and news. An Agent searches for the right records, opens them, extracts the facts, and downloads supporting documents into its sandbox.
```text theme={null}
You are a KYB analyst. The task names a business and its jurisdiction. Verify it.
1. Find the official company registry for the jurisdiction (e.g. Delaware Division of Corporations at icis.corp.delaware.gov for US/Delaware entities, Companies House for the UK). Use the registry's own pages, not gated aggregators. Do NOT use OpenCorporates unless all official sources fail — it is CAPTCHA-protected and unreliable.
2. Confirm the legal name, registration number, incorporation date, and current status from the registry. Note: Delaware's ICIS system does not offer free PDF downloads; certificates require payment. If no free document is available, state this clearly in notes and move on — do not probe dead-end URLs.
3. Check the company name against sanctions lists:
- **OFAC**: https://sanctionssearch.ofac.treas.gov/
- **UN Consolidated List**: https://www.un.org/securitycouncil/content/un-sc-consolidated-list (use the search tool on that page)
- **EU Consolidated List**: Download the public XML from the EU Open Data Portal (search data.europa.eu for "EU financial sanctions consolidated list") — do NOT attempt to log in to webgate.ec.europa.eu/fsd/fsf, which requires authentication.
4. Do NOT query SEC EDGAR — Browserbase and similar private companies do not file with the SEC.
5. Only report facts you can attribute to a source. Capture the source URLs you used. If a fact cannot be verified, leave its field empty and explain why in `notes`.
```
```json theme={null}
{
"type": "object",
"properties": {
"legalName": { "type": "string", "description": "Verified legal business name" },
"registrationNumber": { "type": "string", "description": "Company registration number" },
"status": { "type": "string", "description": "Registry status, e.g. active or dissolved" },
"sanctionsMatch": { "type": "boolean", "description": "Whether the name matched a sanctions list" },
"sources": { "type": "string", "description": "Comma-separated source URLs" },
"notes": { "type": "string", "description": "Anything that couldn't be verified, and why" }
},
"required": ["legalName", "sanctionsMatch"]
}
```
```bash theme={null}
curl -X POST https://api.browserbase.com/v1/agents/runs \
--header "x-bb-api-key: $BROWSERBASE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"agentId": "your-kyb-agent-id",
"task": "Run a KYB check on Browserbase, Inc., incorporated in Delaware, USA",
"browserSettings": { "proxies": true }
}'
```
Documents the Agent downloads land in the run sandbox. Retrieve them through the [Downloads API](/platform/browser/files/downloads); see [managing files](/platform/agents/managing-files) for the full retrieval workflow. When you're ready to keep sensitive inputs (account numbers, dates of birth) out of the task text, move them to [variables](/platform/agents/integrate-api-sdk#passing-variables).
## Monitoring prices and changes
Pricing and inventory live on sites that change their markup, throw interstitials and ads, and hide numbers behind JavaScript. An Agent renders each page, dismisses what's in the way, and returns the same fields every run, so you can diff against the last result and alert on movement.
```text theme={null}
You monitor a product page and report its current state. The task gives the page URL.
1. Open the URL in the task.
2. Dismiss any cookie banner, region prompt, or modal that blocks the page.
3. Read the current price, currency, and availability from the rendered page,
not from cached or list-view data.
Report only what is visible right now. If the price is not shown (out of stock,
gated), leave `price` empty and say why in `notes`.
```
```json theme={null}
{
"type": "object",
"properties": {
"title": { "type": "string", "description": "Product name as shown on the page" },
"price": { "type": "number", "description": "Current price" },
"currency": { "type": "string", "description": "Currency code, e.g. USD" },
"availability": { "type": "string", "description": "in_stock, out_of_stock, or unknown" },
"notes": { "type": "string", "description": "Why a price is missing, if any" }
},
"required": ["availability"]
}
```
```bash theme={null}
curl -X POST https://api.browserbase.com/v1/agents/runs \
--header "x-bb-api-key: $BROWSERBASE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"agentId": "your-monitor-agent-id",
"task": "Check the current price and availability for https://www.amazon.com/Logitech-Programmable-Backlighting-Bluetooth-Rechargeable/dp/B0BKW3LB2B/ref=sr_1_3",
"browserSettings": { "proxies": true }
}'
```
Once an Agent run reveals the underlying request that returns the price, you can replay that endpoint cheaply with the [Fetch API](/platform/fetch/overview) for high-frequency polling, and keep the Agent for sites where the data only renders in a browser. See [pulling the same data on a schedule](/use-cases/agents).
To run the check on a cron, deploy the trigger as a [Function](/platform/runtime/overview) and store each run's `result` so you can diff successive observations.
## Scale to 200+ portals
When you need the same task across hundreds of portals, supplier sites, or government systems, writing one script per target stops scaling: each portal has its own layout and changes on its own schedule. Instead, define **one** Agent whose prompt describes the goal abstractly, then run it across every portal by varying the inputs.
```text theme={null}
You retrieve an invoice from a vendor portal. The task gives the portal URL and the
sign-in credentials.
1. Go to the portal URL in the task and sign in with the credentials it provides.
2. Navigate to the billing or invoices section.
3. Find the most recent invoice and read its number, date, and total.
4. Download the invoice PDF.
Portals differ in layout. Do not rely on fixed labels; find the billing area by
its meaning. If sign-in fails or no invoice exists, return what you found and
explain in `notes` rather than guessing.
```
```json theme={null}
{
"type": "object",
"properties": {
"invoiceNumber": { "type": "string", "description": "Latest invoice number" },
"invoiceDate": { "type": "string", "description": "Invoice date" },
"total": { "type": "number", "description": "Invoice total" },
"currency": { "type": "string", "description": "Currency code, e.g. USD" },
"status": { "type": "string", "description": "retrieved, no_invoice, or login_failed" },
"notes": { "type": "string", "description": "Anything notable about this portal" }
},
"required": ["status"]
}
```
```javascript Node.js theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY,
});
const portals = [
{
portalUrl: "https://vendor-a.com",
username: process.env.VENDOR_A_USERNAME,
password: process.env.VENDOR_A_PASSWORD,
},
{
portalUrl: "https://vendor-b.com",
username: process.env.VENDOR_B_USERNAME,
password: process.env.VENDOR_B_PASSWORD,
},
// ...200 more
];
const runs = await Promise.all(
portals.map((p) =>
bb.agents.runs.create({
agentId: "your-portal-agent-id",
task: "Retrieve the latest invoice from %portalUrl%. Sign in with %username% and %password%.",
variables: {
portalUrl: { value: p.portalUrl },
username: { value: p.username },
password: { value: p.password },
},
browserSettings: { proxies: true },
}),
),
);
console.log(`Started ${runs.length} runs`);
```
```python theme={null}
import asyncio
import os
from browserbase import AsyncBrowserbase
portals = [
{
"portal_url": "https://vendor-a.com",
"username": os.environ["VENDOR_A_USERNAME"],
"password": os.environ["VENDOR_A_PASSWORD"],
},
{
"portal_url": "https://vendor-b.com",
"username": os.environ["VENDOR_B_USERNAME"],
"password": os.environ["VENDOR_B_PASSWORD"],
},
# ...200 more
]
async def main():
async with AsyncBrowserbase(
api_key=os.environ["BROWSERBASE_API_KEY"]
) as bb:
runs = await asyncio.gather(
*(
bb.agents.runs.create(
agent_id="your-portal-agent-id",
task="Retrieve the latest invoice from %portalUrl%. Sign in with %username% and %password%.",
variables={
"portalUrl": {"value": portal["portal_url"]},
"username": {"value": portal["username"]},
"password": {"value": portal["password"]},
},
browser_settings={"proxies": True},
)
for portal in portals
)
)
print(f"Started {len(runs)} runs")
asyncio.run(main())
```
Each portal gets its own browser session, so runs execute concurrently up to your [concurrency limit](/platform/browser/getting-started/create-browser-session). The same Agent handles every portal because variables provide the URL and credentials. Track each run to completion with [Get a run](/reference/api/get-a-run), or follow the [integration guide](/platform/agents/integrate-api-sdk).
## Next steps
Trigger runs, pass variables, and track them to completion.
Tune the prompt and output to make runs faster and more reliable.
Retrieve documents an Agent downloads during a run.
Full request and response reference for a run.
Create, manage, and monitor Agents from Node.js.
Create, manage, and monitor Agents from Python.
# Generate a script
Source: https://docs.browserbase.com/platform/agents/generating-scripts
Turn a completed Agent run into a reusable Stagehand script with the Optimize tool.
Use the Optimize tool to turn a completed Agent run into a reusable [Stagehand](https://docs.stagehand.dev/) script. Optimize reads the run's steps and generates code that follows the same workflow.
This works best after your Agent completes the task successfully. The completed run gives Optimize a working path to translate into code.
## Generate the script
Open the [Browserbase dashboard](https://www.browserbase.com/overview), select **Agents**, and choose the completed run you want to reproduce.
Click **Optimize** in the run view. Optimize uses the run's event history, so you don't need to describe every action again.
Enter this prompt in **Ask about this session**:
```text theme={null}
Can you create a Stagehand script to do the same thing as this Agent run?
```
You can also select **Write a script** from the starter prompts.
Optimize returns a Stagehand script based on the run. Review the URLs, actions, extraction logic, and expected result, then copy the code into your project.
Treat the generated script as a starting point. Run it against the same workflow before you use it with new inputs or sites.
Follow the [Stagehand quickstart](/welcome/quickstarts/stagehand) to install Stagehand, set `BROWSERBASE_API_KEY`, and run the generated file.
Stagehand automatically uses [Model Gateway](/platform/model-gateway/overview) when you use a Browserbase-hosted browser without a provider-specific API key. You can run the generated script with one Browserbase API key and no separate model provider account.
## When to generate a script
Generate a script when you have a successful Agent run and want more control over how the workflow executes. A script lets you:
* Edit individual Stagehand or Playwright steps.
* Add the workflow to an existing application.
* Test changes with your development tools.
* Reuse a known path for repeated tasks.
Keep using an Agent when the workflow needs to adapt across sites, layouts, or tasks that you can't define ahead of time.
## Next steps
Install Stagehand and run your generated script
Use models through your Browserbase API key
Improve an Agent from its run history
Trigger Agent runs from your application
# How it works
Source: https://docs.browserbase.com/platform/agents/how-it-works
How Browserbase Agents work: the execution loop, built-in tools, reusable Agents, run lifecycle, and observability.
Agents are the highest abstraction layer of the Browserbase platform.
You describe a goal in natural language, and Browserbase runs a browser agent that browses, searches, works with files, and returns a result.
No Playwright scripts, no framework code, and no infrastructure to deploy.
## The execution loop
When you create a run, Browserbase starts an autonomous Agent that works in a loop until the task is done:
1. **Observe.** The Agent reads the current page, its memory of prior steps, and the available tools.
2. **Reason.** The Agent's model picks the next action: navigate somewhere, click something, extract data, search the web, run a shell command, or declare success.
3. **Act.** The chosen tool executes and its result flows back into the Agent's context.
4. **Repeat.** The loop continues until the Agent decides the task is complete or reaches a terminal state.
You provide the task; Browserbase handles the [model](/platform/model-gateway/overview), the browser, the tools, and the runtime.
## Built-in tools
The Agent has four built-in tool groups. You can't add custom tools or disable the built-ins yet.
### Browser control
The Agent uses [Stagehand](/welcome/quickstarts/stagehand), the SDK for browser agents, to interact with web pages. It navigates to URLs, clicks elements, types into forms, extracts structured data, and waits for page state to settle. Because the Agent reasons about each page rather than following a fixed script, it adapts to layout changes and per-site differences that break selectors. This is the primary tool for most runs.
### Web search
Before opening a browser, the Agent can search the web to discover URLs or pull quick context, powered by the [Search](/platform/search/overview) and [Fetch](/platform/fetch/overview) APIs. This lets it find the right starting point rather than guessing.
### File system
The Agent works with real files, not just web pages. It reads and writes files in a sandboxed workspace, processes downloaded content like PDFs, and produces output such as spreadsheets. When the Agent triggers a file download, you retrieve the result through the [Downloads API](/platform/browser/files/downloads).
### Shell
The Agent can run commands in the sandboxed [Runtime](/platform/runtime/overview) that backs each run. When data transformation, scripting, or CLI tools are faster than driving a browser, the Agent uses the shell instead.
## Reusable Agents and runs
An **Agent** is a reusable configuration; a **run** is a single execution of a task on a browser session of its own.
Reuse is what lets you scale across many targets. Instead of writing and maintaining one script per portal, create an Agent once and run it across hundreds of sites.
### Create an Agent in the dashboard
The fastest way to build an Agent is the [Agents page in the dashboard](/welcome/quickstarts/agents). Describe the goal in natural language, set a system prompt and a structured output schema, then trigger runs and inspect each step live. The dashboard also surfaces success rate, average duration, and an Optimize tool that tunes the Agent from its past runs.
### Create an Agent with the API
You can also define an Agent programmatically with [Create an Agent](/reference/api/create-an-agent):
* **`systemPrompt`** gives the Agent consistent instructions on every run.
* **`resultSchema`** is a JSON Schema that shapes the Agent's output into typed, repeatable JSON. You can set it on the Agent or override it per run.
* **`variables`** pass sensitive or per-run values, such as account numbers, dates of birth, or confirmation codes, as `%variable%` placeholders the Agent fills in without seeing them inline.
Either way, trigger a run with [Run an Agent](/reference/api/run-an-agent) by passing the `agentId`. A `POST /v1/agents/runs` call with no `agentId` creates a new Agent and its first run in a single call, returning both an `agentId` and a `runId`.
## Run lifecycle
Runs are asynchronous. A run moves through these states after creation:
```
PENDING → RUNNING → COMPLETED
→ FAILED
→ TIMED_OUT
→ STOPPED
```
`PENDING` and `RUNNING` are active states. Either the run is queued or the Agent is working. The remaining states are terminal. Once a run reaches one, it won't change again.
Create a run, poll [Get a run](/reference/api/get-a-run) until it reaches a terminal state, then read the result.
## Configuration and controls
Each run carries production-grade controls. Set supported session controls per run through `browserSettings`:
* **Context** lets a run use a Browserbase context and optionally persist it after browsing.
* **Proxies** route traffic through Browserbase proxies.
* **Verified** enables Browserbase Verified for the session.
## The browser session
The Agent may skip the browser entirely if it can complete the task through web search or fetch alone.
Every run gets a dedicated Browserbase browser session. Browserbase creates the session when the run starts and closes it when the run ends. You access it through the `sessionId` in the run response, using the same [Session APIs](/reference/api/get-a-session) available to any session.
Because each run is a real browser session, you get full observability:
* **Live View** lets you watch the Agent browse in real time.
* **Session Replay** lets you review what the Agent did in the [Session Inspector](/features/session-inspector).
* **Logs** capture console output and network activity from the session.
A [Browserbase Function](/platform/runtime/overview) provides the serverless compute that backs each run.
## Results and messages
When a run reaches `COMPLETED`, [Get a run](/reference/api/get-a-run) returns:
* **`result`:** the Agent's structured output. Present when you supplied a `resultSchema`, and conforming to that schema.
* **`sessionId`:** the browser session ID for replay and debugging.
To follow what the Agent did step by step, use [List run messages](/reference/api/list-run-messages). It returns messages in chronological order, conforming to the [AI SDK UIMessage format](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message). Each message has a `role` and a `parts` array with typed content blocks (text, tool calls, reasoning, and files). Poll this endpoint while the run is active to stream progress, or call it once after completion for the full transcript.
## When to use Agents
| Use Agents when | Consider another tool when |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| You want a browser agent to complete a web task from a natural language goal. | You need deterministic, code-driven browser control: use [Sessions](/reference/api/create-a-session). |
| You want to scale across many sites without writing one script per target. | You want to deploy custom browser logic as serverless functions: use [Functions](/functions/quickstart). |
| You don't want to maintain Playwright, Stagehand, model, or runtime orchestration. | You only need lightweight read-only context: use [Fetch](/platform/fetch/overview) or [Search](/platform/search/overview). |
## Next steps
Create and poll your first Agent run
Define a reusable Agent with a system prompt and result schema
Debug and replay Agent sessions
Get Agents past anti-bot systems, CAPTCHAs, and auth walls
# Integrating Agents
Source: https://docs.browserbase.com/platform/agents/integrate-api-sdk
Create and manage Agents, trigger runs, pass variables, and track runs to completion.
Once you've built an Agent in the [dashboard](/welcome/quickstarts/agents), you can integrate it into your application with the official SDK/API: manage reusable Agents, trigger runs, pass per-run values, track each run to completion, and read the result.
Runs are asynchronous. You create a run, then track it to a terminal state by polling.
## Install an SDK
```bash theme={null}
npm install @browserbasehq/sdk
```
```bash theme={null}
pip install browserbase
```
Initialize the client once:
```typescript Node.js theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY!,
});
```
```python theme={null}
import os
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
```
See the [Node.js SDK reference](/reference/sdk/nodejs#agents) or [Python SDK reference](/reference/sdk/python#agents) for every Agents SDK method, parameter, and response field.
To scaffold the integration quickly, open an Agent's run view in the dashboard and use **Build API in** to open the Agent's API prompt in a coding tool like Cursor.
## Endpoints at a glance
**Agents** define reusable configuration.
| Endpoint | Use |
| ------------------------------------------------- | --------------------------------------------------------------- |
| [Create an Agent](/reference/api/create-an-agent) | Define a reusable Agent with a system prompt and result schema. |
| [List Agents](/reference/api/list-agents) | Page through your Agents. |
| [Get an Agent](/reference/api/get-an-agent) | Fetch a single Agent. |
| [Update an Agent](/reference/api/update-an-agent) | Change an Agent's configuration. |
| [Delete an Agent](/reference/api/delete-an-agent) | Remove an Agent. |
**Runs** are single executions of a task.
| Endpoint | Use |
| ----------------------------------------------------- | -------------------------------------------------- |
| [Run an Agent](/reference/api/run-an-agent) | Start a run, optionally against an existing Agent. |
| [List runs](/reference/api/list-runs) | Page through runs, filtered by Agent or status. |
| [Get a run](/reference/api/get-a-run) | Fetch a run's status and result. |
| [List run messages](/reference/api/list-run-messages) | Stream the run's step-by-step transcript. |
## Trigger a run
Start a run with [Run an Agent](/reference/api/run-an-agent). Pass an `agentId` to run an existing Agent, along with per-run options like `variables`, `resultSchema`, and `browserSettings`.
```typescript Node.js theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY!,
});
const { runId } = await bb.agents.runs.create({
agentId: "a1b2c3d4-5678-90ab-cdef-1234567890ab",
task: "Find the best matching open role at Browserbase",
browserSettings: { proxies: true },
});
console.log("Run started:", runId);
```
```python theme={null}
import os
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
run = bb.agents.runs.create(
agent_id="a1b2c3d4-5678-90ab-cdef-1234567890ab",
task="Find the best matching open role at Browserbase",
browser_settings={"proxies": True},
)
print("Run started:", run.run_id)
```
```bash theme={null}
curl -X POST https://api.browserbase.com/v1/agents/runs \
--header "x-bb-api-key: $BROWSERBASE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"agentId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"task": "Find the best matching open role at Browserbase",
"browserSettings": { "proxies": true }
}'
```
A call with no `agentId` creates a new Agent and its first run, returning both an `agentId` and a `runId`. See [Run an Agent](/reference/api/run-an-agent) for every field.
## Manage reusable Agents with the SDK
The SDKs support the complete reusable Agent lifecycle:
```typescript Node.js theme={null}
const agent = await bb.agents.create({
name: "Job Finder",
systemPrompt: "Find the best matching role on the company's official job board.",
resultSchema: {
type: "object",
properties: {
title: { type: "string" },
url: { type: "string" },
},
required: ["title", "url"],
},
});
const current = await bb.agents.retrieve(agent.agentId);
const updated = await bb.agents.update(agent.agentId, {
name: "Official Job Board Finder",
systemPrompt: "Use only the company's official careers site or applicant tracking system.",
});
const page = await bb.agents.list({ limit: 20 });
await bb.agents.delete(agent.agentId);
```
```python theme={null}
agent = bb.agents.create(
name="Job Finder",
system_prompt="Find the best matching role on the company's official job board.",
result_schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"url": {"type": "string"},
},
"required": ["title", "url"],
},
)
current = bb.agents.retrieve(agent.agent_id)
updated = bb.agents.update(
agent.agent_id,
name="Official Job Board Finder",
system_prompt="Use only the company's official careers site or applicant tracking system.",
)
page = bb.agents.list(limit=20)
bb.agents.delete(agent.agent_id)
```
Deleting an Agent doesn't affect runs that already referenced it. Updates are partial: omitted fields remain unchanged.
## Passing variables
Use `variables` to pass per-run dynamic values without writing them into the prompt. Each variable pairs a `value` the placeholder resolves to with an optional `description` that tells the Agent when to use it. Reference a variable in the task or system prompt as `%variableName%`.
```json theme={null}
{
"agentId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"task": "Check in to my flight using confirmation %confirmation%",
"variables": {
"confirmation": {
"value": "XYZ123",
"description": "The flight confirmation code to enter at check-in"
}
}
}
```
Variables suit values like account numbers, dates of birth, and confirmation codes. The Agent fills the placeholder without the value appearing inline in the task.
## Tracking a run to completion
A run moves through these states:
```
PENDING → RUNNING → COMPLETED
→ FAILED
→ TIMED_OUT
→ STOPPED
```
`PENDING` and `RUNNING` are active. The rest are terminal.
### Poll for the result
Poll [Get a run](/reference/api/get-a-run) until the run reaches a terminal state, then read `result`:
```typescript Node.js theme={null}
async function pollRun(runId: string) {
const terminal = ["COMPLETED", "FAILED", "STOPPED", "TIMED_OUT"];
while (true) {
const run = await bb.agents.runs.retrieve(runId);
if (terminal.includes(run.status)) return run;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
const run = await pollRun(runId);
console.log("Result:", run.result);
```
```python theme={null}
import time
def poll_run(run_id):
terminal = {"COMPLETED", "FAILED", "STOPPED", "TIMED_OUT"}
while True:
run = bb.agents.runs.retrieve(run_id)
if run.status in terminal:
return run
time.sleep(2)
run = poll_run(run_id)
print("Result:", run.result)
```
```bash theme={null}
curl https://api.browserbase.com/v1/agents/runs/$RUN_ID \
--header "x-bb-api-key: $BROWSERBASE_API_KEY"
```
### Read or stream progress
To follow what the Agent is doing while it runs, poll [List run messages](/reference/api/list-run-messages). Save the response's `nextSince` and pass it back as `since` to fetch only newer messages:
```typescript Node.js theme={null}
let since: string | undefined;
while (true) {
const page = await bb.agents.runs.listMessages(runId, {
since,
limit: 100,
});
for (const item of page.data) {
console.log(item.message);
}
since = page.nextSince ?? since;
const run = await bb.agents.runs.retrieve(runId);
if (!["PENDING", "RUNNING"].includes(run.status)) break;
await new Promise((resolve) => setTimeout(resolve, 2000));
}
```
```python theme={null}
import time
since = None
while True:
params = {"limit": 100}
if since:
params["since"] = since
page = bb.agents.runs.list_messages(run_id, **params)
for item in page.data:
print(item.message)
since = page.next_since or since
run = bb.agents.runs.retrieve(run_id)
if run.status not in {"PENDING", "RUNNING"}:
break
time.sleep(2)
```
```bash theme={null}
curl "https://api.browserbase.com/v1/agents/runs/$RUN_ID/messages?since=$LAST_MESSAGE_ID" \
--header "x-bb-api-key: $BROWSERBASE_API_KEY"
```
Messages conform to the [AI SDK UIMessage format](https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message). Each item includes an `id`, `createdAt`, and a `message` with a `role` and `content`. Content can be text or typed parts such as reasoning, files, tool calls, and tool results.
Integration is poll-based today: track runs with [Get a run](/reference/api/get-a-run) and [List run messages](/reference/api/list-run-messages). Webhook notifications are planned.
## Listing and filtering
Both list endpoints use cursor pagination. Pass the `nextCursor` from a response back as `cursor` to fetch the next page.
[List runs](/reference/api/list-runs) filters by `agentId`, `status`, and a `startAt`/`endAt` time range:
```typescript Node.js theme={null}
const runs = await bb.agents.runs.list({
agentId,
status: "COMPLETED",
limit: 20,
});
if (runs.nextCursor) {
const nextPage = await bb.agents.runs.list({
agentId,
status: "COMPLETED",
limit: 20,
cursor: runs.nextCursor,
});
}
```
```python theme={null}
runs = bb.agents.runs.list(
agent_id=agent_id,
status="COMPLETED",
limit=20,
)
if runs.next_cursor:
next_page = bb.agents.runs.list(
agent_id=agent_id,
status="COMPLETED",
limit=20,
cursor=runs.next_cursor,
)
```
```bash theme={null}
curl "https://api.browserbase.com/v1/agents/runs?agentId=$AGENT_ID&status=COMPLETED&limit=20" \
--header "x-bb-api-key: $BROWSERBASE_API_KEY"
```
[List Agents](/reference/api/list-agents) pages through your Agents with the same `cursor` pagination pattern.
## Next steps
Complete Agents SDK method and type reference
Complete Agents SDK method and type reference
Full request and response reference for a run
Stream a run's step-by-step transcript
Retrieve files an Agent downloads during a run
The execution loop, run lifecycle, and observability
# Managing files
Source: https://docs.browserbase.com/platform/agents/managing-files
How Browserbase Agents work with files, and retrieve downloads.
Agents work with real files, not just page content. Each run gets a sandboxed file workspace where the Agent can read and write files, process documents it downloads, and produce output. This is what lets an Agent fetch a PDF, read a spreadsheet, or pull a report rather than only scraping text from a page.
For the broader tool set, see [How it works](/platform/agents/how-it-works). For the API that returns files, see the [Downloads API](/platform/browser/files/downloads).
## What the Agent can do with files
* **Read and write files** in its sandboxed workspace during a run.
* **Process downloaded content** such as PDFs and spreadsheets.
* **Produce output files** like CSVs or spreadsheets from data it gathers.
* **Extract small inline tables** directly from a page when the data is compact.
The Agent decides when files are the fastest path. A run that needs to compare figures across a downloaded report, for example, may download the file and process it rather than reading the page.
## Retrieving files the Agent downloads
When the Agent downloads a file during a run, Browserbase stores it against the run's browser session. Retrieve it with the [Downloads API](/platform/browser/files/downloads) using the `sessionId` from the run.
First, get the run's `sessionId` from [Get a run](/reference/api/get-a-run). Then list the downloads for that session with [List downloads](/reference/api/list-downloads):
```typescript Node.js theme={null}
const response = await fetch(
`https://api.browserbase.com/v1/downloads?sessionId=${sessionId}`,
{
headers: { "x-bb-api-key": process.env.BROWSERBASE_API_KEY! },
},
);
const { downloads } = await response.json();
console.log(downloads);
```
```python theme={null}
import os
import requests
response = requests.get(
"https://api.browserbase.com/v1/downloads",
headers={"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"]},
params={"sessionId": session_id},
)
downloads = response.json()["downloads"]
print(downloads)
```
```bash theme={null}
curl "https://api.browserbase.com/v1/downloads?sessionId=$SESSION_ID" \
--header "x-bb-api-key: $BROWSERBASE_API_KEY"
```
Fetch a single file by ID with [Get a download](/reference/api/get-a-download). You can also filter the list by `filename`, `mimeType`, or size. See the [Downloads](/platform/browser/files/downloads) guide for the full workflow.
## Use cases
Files unlock the document-heavy tasks that scripts struggle with:
* **Document fetching**: SOC2 reports, authorization forms, and transaction documents from vendor and provider sites.
* **Government and real estate records**: tax documents and property records that download as PDFs.
* **Data export**: gather records across pages, then write a single spreadsheet as output.
## Limitations
The file workflow is still growing. Plan around these limits:
* **You can't upload files to an Agent yet.** The Agent works with files it creates or downloads during the run.
* **Large, paginated tabular data can't be reliably exported inline.** Trigger a download and retrieve the file through the [Downloads API](/platform/browser/files/downloads) instead of asking the Agent to extract a large table.
* **Large files (over 1 MB) should flow through downloads,** not inline extraction. The Agent can extract small `
` elements directly, but bigger payloads belong in a download.
## Next steps
Retrieve files an Agent downloads during a run
The execution loop, built-in tools, and run lifecycle
Trigger runs and track them from your application
API reference for listing a session's downloads
# Optimizing Agents
Source: https://docs.browserbase.com/platform/agents/optimizing-agents
Use the Optimize tool, tune your prompt and schema, and measure Agent quality to make runs faster and more reliable.
A first run is always exploratory. The Agent meets the site for the first time, so it backtracks, retries, and takes longer than it needs to. Optimization turns that exploration into a faster, more reliable path for every run that follows.
You have two levers: the **Optimize tool** in the dashboard, which suggests changes from past runs, and the Agent configuration you control directly.
## The Optimize tool
The Optimize tool lives in the Agent run view in the dashboard. Click **Optimize** at the top right to open the **Optimize this session** panel. It reads the run's event history and helps you turn what the Agent did into a better system prompt.
From the Agent's run view, open the run you want to improve and click **Optimize**.
Choose a starter prompt or type your own question in **Ask about this session**. The starter prompts are:
* **Make this faster**: find steps that wasted time.
* **What went wrong**: diagnose a failed or off-target run.
* **Alternative approaches**: suggest a different way to do the task.
* **Write a script**: [generate a Stagehand script](/platform/agents/generating-scripts) that accomplishes the task.
The tool returns a step-by-step analysis of the run: the specific actions that cost time, how much each cost, and what went well. For a job-finding Agent it commonly flags an unnecessary opening screenshot, scrolling to discover roles instead of reading the page's accessibility tree, and scrolling a detail page instead of reading the URL directly.
When you accept its offer to change the Agent, the tool shows a **Proposed config update** with a `systemPrompt` diff (added and removed lines). Use **Edit** to adjust the wording, then **Update & run again** to save the new system prompt and start a fresh run so you can confirm the gain.
**When should you use the Optimize tool?**
* When you want to speed up an Agent that works but wanders.
* When a run fails or returns the wrong result and you want to know why.
* When you want a second opinion on how to approach the task.
## Best practices
The Optimize tool proposes prompt changes for you, but you can optimize directly through the Agent configuration. These levers map to real fields on [Create an Agent](/reference/api/create-an-agent) and [Run an Agent](/reference/api/run-an-agent).
* **Tighten the `systemPrompt`.** Give explicit steps, name the sources the Agent should and shouldn't use, and define what success looks like. A scoped prompt removes guesswork that slows runs down.
* **Set a `resultSchema`.** A JSON Schema forces structured, repeatable output and stops the Agent from returning prose when you need fields.
* **Use `variables` for per-run values.** Pass account numbers, dates, or confirmation codes as `%variable%` placeholders instead of rewriting the prompt for each run.
* **Prefer official sources.** Point the Agent at a site's own pages or its real applicant tracking system, and steer it away from gated aggregators that block automated access.
## Measuring Agent quality
Optimize against numbers, not impressions. Use the dashboard Agent page and run history to watch the same signals over time:
| Field | Meaning |
| ----------------- | -------------------------------- |
| `completionRate` | Fraction of runs that completed. |
| `failRate` | Fraction of runs that failed. |
| `timeoutRate` | Fraction of runs that timed out. |
| `averageDuration` | Average run duration in seconds. |
A rising `timeoutRate` or `failRate` is the signal to revisit the prompt or run the Optimize tool again.
## Limitations
* **Optimize suggestions are advisory.** They come from past runs, so review a change before accepting it and confirm the gain on the re-run.
* **Agents can't call custom tools yet.** You can give an Agent skills through the `systemPrompt`, but custom tool definitions aren't supported in the current version.
## Next steps
Trigger a run after updating an Agent configuration
Define a reusable Agent with a system prompt and result schema
Build and optimize an Agent in the dashboard
Trigger runs and track them from your application
# Agents
Source: https://docs.browserbase.com/platform/agents/overview
Run autonomous browser agents on Browserbase with one API call.
Create your first Agent run and poll for the result.
Learn how Agents work, what tools they use, and how runs progress.
Create Agents and runs from Node.js.
Create Agents and runs from Python.
Endpoint reference for starting an Agent run.
Understand when to use Agents versus building a custom browser-agent loop.
## Overview
Agents are the lowest-friction way to automate work on the web with Browserbase. Describe the task in natural language, and Browserbase runs an autonomous Agent that can browse, click, type, search, extract data, use files, and return a result.
Use Agents when you want Browserbase to own the browser loop for you. You do not need to write Playwright scripts, wire up Stagehand, provision model providers, or deploy runtime infrastructure. Each run gets a dedicated Browserbase browser session with the same observability tools available to standard sessions.
## Quick reference
| Concept | What it does |
| ------------ | ---------------------------------------------------------------------------------------- |
| **Agent** | The autonomous Browserbase Agent that can perform one or more runs. |
| **Run** | A single natural language task executed by an Agent. |
| **Messages** | The chronological transcript of what the Agent did during a run. |
| **Session** | The Browserbase browser session that backs the run for live view, recording, and replay. |
## How it works
Create a run with a natural language instruction and optional browser settings.
Browserbase creates the browser session, runtime, tools, and model loop needed to complete the task.
The Agent chooses the right tools, navigates the web, interacts with pages, and extracts the requested result.
Poll the run, read its messages, and review the browser session when you need to debug.
## Create a run
Create a run with the SDK or `/v1/agents/runs` endpoint. The response includes an `agentId` and a `runId`.
```typescript Node.js theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY!,
});
const { agentId, runId } = await bb.agents.runs.create({
task: "Go to Hacker News and return the top 3 stories with their titles and URLs",
});
console.log({ agentId, runId });
```
```python theme={null}
import os
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
run = bb.agents.runs.create(
task="Go to Hacker News and return the top 3 stories with their titles and URLs",
)
print(run.agent_id, run.run_id)
```
```bash theme={null}
curl -X POST https://api.browserbase.com/v1/agents/runs \
--header "x-bb-api-key: $BROWSERBASE_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"task": "Go to Hacker News and return the top 3 stories with their titles and URLs"
}'
```
## Run lifecycle
Agent runs are asynchronous. Create a run, poll it until it reaches a terminal state, then read the final result and session details.
| Status | Description |
| ----------- | --------------------------------- |
| `PENDING` | The run is queued. |
| `RUNNING` | The Agent is working on the task. |
| `COMPLETED` | The run finished successfully. |
| `FAILED` | The run failed. |
| `STOPPED` | The run was stopped by request. |
| `TIMED_OUT` | The run exceeded its timeout. |
## Built-in capabilities
Agents use the Browserbase platform out of the box:
* **Browser automation**: navigate pages, click, type, wait, observe, and extract using Stagehand.
* **Search**: discover relevant pages before opening a browser.
* **Files**: read, write, and process files in the run environment.
* **Shell**: run commands in the sandbox when code or CLIs are the fastest path.
* **Identity and access**: use Browserbase browser settings for proxies, CAPTCHA solving, and Agent Identity.
* **Observability**: inspect live sessions, recordings, replay, and run messages.
## When to use Agents
| Use Agents when | Use another Browserbase tool when |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| You want an autonomous Agent to complete a web task from a natural language instruction. | You need deterministic browser control in your own code: use [browser sessions](/platform/browser/getting-started/create-browser-session). |
| You do not want to maintain Playwright, Stagehand, model, or runtime orchestration. | You want to deploy custom browser logic: use [Functions](/platform/runtime/overview). |
| You want built-in search, browsing, files, shell, and observability in one run. | You only need cheap read-only recon: use [Search](/platform/search/overview) or [Fetch](/platform/fetch/overview). |
## Next steps
Create and poll your first Agent run.
Learn the execution loop, built-in tools, run lifecycle, and observability.
See request and response fields for creating a run.
Use every Agents SDK method from Node.js.
Use every Agents SDK method from Python.
Give Agents stronger trust signals for protected websites.
## Enterprise data controls
Agents is currently outside the scope of [Zero Data Retention (ZDR)](/account/enterprise/zero-data-retention) and [Bring Your Own Storage (BYOS)](/account/enterprise/byos-setup-guide). [Contact us](https://www.browserbase.com/contact) if you need ZDR, BYOS, or a BYO model key.
# Browser extensions
Source: https://docs.browserbase.com/platform/browser/core-features/browser-extensions
Augment your browser sessions with your own Chrome extensions.
You can load your own Chrome extensions into Browserbase sessions.
## Sample extension
Here's a simple example extension that modifies page titles. It consists of two files:
```js manifest.json theme={null}
{
"manifest_version": 3,
"version": "1.0",
"name": "My Test Extension",
"description": "Test of a simple browser extension",
"content_scripts": [
{
"matches": [
"https://www.sfmoma.org/*"
],
"js": [
"content-script.js"
]
}
]
}
```
```js content-script.js theme={null}
document.title += " -- Title updated by browser extension";
```
You can download this sample extension
[here](http://browser-tests-alpha.vercel.app/demo-extension.zip). The
extension must be in a `.zip` file format with a `manifest.json` at the root.
The file must be less than or equal to 100 MB.
## Upload your extension
Once you have your extension files zipped up, you can upload it using the SDK:
```javascript theme={null}
import { Browserbase } from '@browserbasehq/sdk';
import fs from 'fs';
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const file = fs.createReadStream('extension.zip');
const extension = await bb.extensions.create({ file });
const extensionId = extension.id;
console.log(`Extension uploaded with ID: ${extensionId}`);
```
```python theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
with open("extension.zip", "rb") as f:
extension = bb.extensions.create(file=f)
extension_id = extension.id
print(f"Extension uploaded with ID: {extension_id}")
```
## Create a session with your extension
To use your extension, create a new session with the extension enabled:
```javascript theme={null}
import { Browserbase } from '@browserbasehq/sdk';
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create({
extensionId: 'your-extension-id'
});
console.log(`Session created with ID: ${session.id}`);
```
```python theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
extension_id="your-extension-id"
)
print(f"Session created with ID: {session.id}")
```
Starting a new session with an extension can increase the session creation
time. The browser must be restarted to load the extension, which itself has
nonzero load time.
## Verify the extension
To verify your extension is working:
1. Connect to your session using Playwright or Selenium
2. Navigate to a page where your extension should be active
3. Check for the expected modifications or behaviors
For the sample extension above, navigate to [https://www.sfmoma.org](https://www.sfmoma.org) and verify that the page title has been modified with the additional text.
# Contexts
Source: https://docs.browserbase.com/platform/browser/core-features/contexts
Reuse cookies, authentication, and application data across browser sessions.
Contexts allow you to **persist user data across multiple browser sessions**, enabling smoother automation, seamless authentication, and faster end-to-end workflows.
By default, each Browserbase session starts with a fresh user data directory, so cookies and application data reset between sessions. With Contexts, you can reuse this data across sessions, making automation workflows more reliable and eliminating repeated logins. Browser cookies are stored in the [user data directory](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/user_data_dir.md).
**Contexts are configured by:**
1. Creating a Context via the [Contexts API](/reference/api/create-a-context)
2. Passing the Context ID into the [Create Sessions API](/reference/api/create-a-session)
View or run the example template [here](https://www.browserbase.com/templates/context)
### Why use Contexts?
* **Reusing cookies & session data**: Maintain login states across multiple sessions without needing to log in repeatedly.
* **Preserving authentication**: Store and reuse authentication tokens, reducing the need to re-enter credentials.
* **Retaining application data**: Persist localStorage, IndexedDB, and other site-specific data written by web applications.
* **Faster workflows**: Skip login flows and re-authentication steps, reducing the total time of your automation runs.
A Context persists the contents of the Chromium user data directory, including:
* **Cookies**: session cookies, explicitly backed up and restored between sessions
* **localStorage**: per-origin key-value storage
* **IndexedDB**: structured client-side databases
* **Session Storage**: tab-scoped storage
* **Service Workers**: site-controlled caching and offline support
* **Web Data**: form autofill entries and saved form data
* **Browser preferences**: site-level settings, permissions, and security state (e.g., HSTS)
Contexts don't include the browser's HTTP cache (images, CSS, JS, fonts). Each session fetches page assets fresh from the network. However, sites that use Service Workers to cache assets can see improved page load times, since the Service Worker and its cache are persisted across sessions.
Context data can include stored credentials and other sensitive browsing
data. Because of this, Contexts are uniquely encrypted at rest to ensure
security.
## Create a Context
To create a Context, use the Create Context API. This returns a **unique Context ID** that you can pass into new sessions to persist data.
You can optionally set a `name` to label the Context. Names are unique within a project (compared case-insensitively), and leading or trailing whitespace is trimmed.
```ts SDK theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const context = await bb.contexts.create({
name: "my-context",
});
console.log("Context ID:", context.id);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
context = bb.contexts.create(name="my-context")
print("Context ID:", context.id)
```
```bash cURL theme={null}
curl -X POST https://api.browserbase.com/v1/contexts \
-H "Content-Type: application/json" \
-H "X-BB-API-Key: $BROWSERBASE_API_KEY" \
-d '{"name": "my-context"}'
```
## Use a Context
After creating a Context, use it in a new session to reuse cookies, authentication, and application data. This creates a returning user experience, speeding up your workflow and eliminating repeated logins.
After closing a session with `persist: true`, wait a few seconds before reusing the Context to ensure data is synchronized.
Here's an example of how to use a Context in a new session, **this example uses the Context ID from the previous example**.
```ts SDK theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
// Use the context ID from the previous example
const contextId = "";
const session = await bb.sessions.create({
browserSettings: {
context: {
id: contextId,
persist: true,
},
},
});
console.log("Session URL: https://browserbase.com/sessions/" + session.id);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
# Use the context ID from the previous example
context_id = ""
session = bb.sessions.create(
browser_settings={
"context": {
"id": context_id,
"persist": True
}
}
)
print("Session URL: https://browserbase.com/sessions/" + session.id)
```
### Set persist
If you need to store new cookies, authentication tokens, or application data, you must set `persist: true` when creating a session. This ensures that any changes made during the session (such as logging in or saving site preferences) are retained for future sessions instead of being lost when the session ends. The data will be saved when the session closes.
`persist: false` prevents changes from being saved to the Context. It's useful for less common use cases, such as:
* **Read-only session**: If you need to access saved cookies or data without modifying them.
* **Prevent session state changes**: Avoids overwriting stored login tokens or user data.
## Login workflow
A typical flow to persist a website login across Browserbase sessions would be as follows:
1. **Create a Context** (get a `contextId`).
2. **Start a first Browserbase session** with that `contextId` and `persist: true`.
3. **Log in** to a website inside this first session, either manually through the [live view feature](/platform/browser/observability/session-live-view#getting-started) or programmatically.
4. **End this session**.
5. **Wait a few seconds** to ensure that the Context is updated.
6. **Start a second Browserbase session** (and future sessions) with the **same** `contextId` and visit the same website. You should now be signed in automatically without having to repeat the login process.
### Login best practices
Browserbase Contexts don't expire (see [Context expiration](#context-expiration)), but sites can still force a log out. Here are a few additional best practices:
* **Avoid simultaneous logins**: Avoid having multiple sessions using the same Context at once. Sites may force a log out.
* **Use a consistent geolocation**: Some sites check user location. Use a [geolocated proxy](/platform/identity/proxies#set-proxy-geolocation) for these cases.
* **One Context per site, per login**: Prevent an individual Context from becoming too large, which can slow down your session.
## Context expiration
Contexts **live indefinitely** on Browserbase's infrastructure. Once created, a Context persists until you explicitly delete it or it becomes invalidated. You can create a Context once and reuse it across sessions for weeks or months.
However, there are several ways a Context can become invalid or unusable:
### Client-side invalidation
* **Explicit deletion**: Calling the [Delete Context API](/reference/api/delete-a-context) permanently removes the Context
* **Project deletion**: If the parent project is deleted, all associated Contexts are removed
* **Account changes**: Account suspension or deletion will invalidate all Contexts
### Application-level invalidation
While the context itself persists, the **data stored within it** can become stale or invalid due to external factors:
* **Session expiration**: Websites may expire authentication cookies after a set period (e.g., 30 days), requiring re-authentication even with a persisted Context
* **Password changes**: If you change your password on a website, stored session cookies may be invalidated by that website
* **Server-side logout**: Websites can invalidate sessions server-side (e.g., "log out of all devices")
* **Token revocation**: OAuth tokens or API keys stored in cookies may be revoked by the service provider
* **Security events**: Websites may force re-authentication after detecting suspicious activity
To handle application-level invalidation, implement checks in your automation
to detect logged-out states and re-authenticate when necessary.
## Delete a Context
When you no longer need a Context, delete it using the [Delete Context API](/reference/api/delete-a-context). Once deleted, a Context can't be used to create new sessions.
Deleting a Context is permanent and can't be undone.
```typescript SDK theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const contextId = "";
await bb.contexts.delete(contextId);
console.log("Context deleted");
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
context_id = ""
bb.contexts.delete(context_id)
print("Context deleted")
```
```bash cURL theme={null}
curl -X DELETE https://api.browserbase.com/v1/contexts/ \
-H "X-BB-API-Key: $BROWSERBASE_API_KEY"
```
Once you set up Contexts, follow the authentication guide to easily log into
websites.
# Core features
Source: https://docs.browserbase.com/platform/browser/core-features/overview
The building blocks for powerful browser automation with Browserbase
Persist cookies, authentication state, and application data across sessions, so you don't need to log in on every run.
Configure the screen size and resolution of your browser sessions to match any device or layout requirement.
Tag sessions with custom key-value data to organize, filter, and query your automation runs.
Load your own Chrome extensions into sessions to augment browser behavior or bypass site-specific challenges.
## Quick reference
| Feature | What it does | When to use it |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| [**Contexts**](/platform/browser/core-features/contexts) | Persists cookies, auth tokens, and browser storage across sessions | Any workflow that requires login: avoids re-authenticating on every run |
| [**Viewports**](/platform/browser/core-features/viewports) | Sets the browser window width and height | Visual testing, screenshots, or layouts that are viewport-dependent |
| [**Session metadata**](/platform/browser/core-features/session-metadata) | Attaches custom JSON to a session for querying later | Organizing sessions by test run, environment, team, or status |
| [**Browser extensions**](/platform/browser/core-features/browser-extensions) | Loads a Chrome extension into the session | Custom page behavior, ad blocking, or site-specific workarounds |
## All features are set at session creation
Every core feature is configured when you create a session. Pass your settings in the `browserSettings` object:
```typescript theme={null}
const session = await bb.sessions.create({
browserSettings: {
viewport: { width: 1920, height: 1080 },
solveCaptchas: true,
},
context: { id: contextId, persist: true },
});
```
See the [Create Session API reference](/reference/api/create-a-session) for the full list of options.
# Metadata
Source: https://docs.browserbase.com/platform/browser/core-features/session-metadata
Tag and query sessions with custom data
As your number of sessions grows, session metadata helps you organize and access them with customizable labels.
Session metadata can be attached to a session and later queried via the [List Sessions API](/reference/api/list-sessions).
### Why use metadata?
Consider running automated tests across multiple browser sessions. Without metadata, you'd struggle to track which session belongs to which test run. With metadata, you can attach a "run ID" to each session and easily query them later. Similarly, you might want to track the status of downloads or associate sessions with specific projects or teams. All of these are possible with metadata.
### Structure
Session metadata is a simple but flexible structure:
* All metadata is a customizable JSON object
* Your JSON object must be under 512 characters
* Data is organized in a nested structure using fields (no arrays supported yet)
* Only string values are supported for querying (convert numbers and booleans to strings)
* Metadata persists throughout the entire session lifecycle
Here's what a typical metadata structure looks like:
```json theme={null}
{
"env": "staging"
}
```
A more extensive, nested object might look like:
```json theme={null}
{
"run": {
"id": "run-abc-123"
},
"env": "staging",
"team": "platform"
}
```
## Attach metadata to a session
Pass the `userMetadata` parameter when creating a session via the [Create Session](/reference/api/create-a-session) endpoint. This metadata can be any JSON-serializable object.
Below is an example that attaches `{"env": "staging"}` to a session.
```javascript SDK theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env["BROWSERBASE_API_KEY"]! });
const session = await bb.sessions.create({
userMetadata: {
env: "staging",
},
});
console.log("Session URL: https://browserbase.com/sessions/" + session.id);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
user_metadata={
"env": "staging",
},
)
print(f"Session URL: https://browserbase.com/sessions/{session.id}")
```
## Query sessions by metadata
Pass a query string to the `q` parameter in [List Sessions](/reference/api/list-sessions) to filter sessions by metadata. The query string format is always `user_metadata['path']['to']['field']:'value'`.
Below is an example that finds all sessions with `{"env": "staging"}`.
```javascript SDK theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env["BROWSERBASE_API_KEY"]! });
const query = "user_metadata['env']:'staging'";
const sessions = await bb.sessions.list({ q: query });
console.log(sessions);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
query = "user_metadata['env']:'staging'"
sessions = bb.sessions.list(q=query)
print(sessions)
```
```bash cURL theme={null}
curl "https://api.browserbase.com/v1/sessions?q=user_metadata%5B%27env%27%5D%3A%27staging%27" \
-H "X-BB-API-Key: $BROWSERBASE_API_KEY"
```
URL encode the query string when calling the API directly to ensure it's properly parsed. The SDKs handle this automatically. In JavaScript, you can use encodeURIComponent("your-query-string-here") to do this.
If no sessions match, the API returns an empty list `[]`.
## Best practices
Use descriptive, consistent naming and keep your metadata structure shallow and intuitive.
**Tip:** Need to query numbers or booleans? Convert them to strings:
```json theme={null}
{
"priority": "5",
"active": "true"
}
```
## Limitations
* JSON object must be under 512 characters (think `JSON.stringify` and measuring the resulting length)
* Only field queries are supported (no array querying)
* Only string equality checks are available
* Only the `user_metadata` base is supported
# Viewports
Source: https://docs.browserbase.com/platform/browser/core-features/viewports
Configure viewport sizes for your sessions
A viewport defines the visible area of a web page in a browser window. While setting a custom viewport is optional in Browserbase, it can be helpful for specific use cases, such as visual testing, screenshot generation, or automations that rely on precise layout behavior.
## Configuring the viewport
Use the `viewport` field when creating a session to specify the desired width and height. Below are examples in both Node.js and Python SDKs.
```typescript SDK theme={null}
import Browserbase from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
async function createSession() {
const session = await bb.sessions.create({
browserSettings: {
viewport: {
width: 1920,
height: 1080
}
},
});
console.log(`Session URL: https://browserbase.com/sessions/${session.id}`);
return session;
}
const session = createSession();
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
def createSession():
session = bb.sessions.create(
browser_settings={
"viewport": {
"width": 1920,
"height": 1080
},
},
)
print(f"Session URL: https://browserbase.com/sessions/{session.id}")
return session
session = createSession()
```
Verified sessions use a fixed viewport managed by Browserbase. Custom viewport dimensions are not supported with Verified. See the [Verified customization guide](/platform/identity/verified-customization) for more details.
## Setting the viewport with Puppeteer
If you're using Puppeteer, it applies an `800 x 600` viewport by default. To customize these dimensions, you'll also need to set `defaultViewport` to `null` as shown below.
```typescript Node.js theme={null}
const session = await bb.sessions.create({
browserSettings: {
viewport: {
width: 1920,
height: 1080
}
},
});
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
defaultViewport: null // Prevents 800x600 default viewport
});
```
For a full list of session options and configuration fields, check out the [API reference for creating a session](/reference/api/create-a-session).
# Downloads
Source: https://docs.browserbase.com/platform/browser/files/downloads
Triggering and retrieving downloaded files
Unlike screenshots saved locally, files downloaded during browser automation are stored in Browserbase's cloud storage. Retrieve them using the Browserbase API.
A common use case for headless browsers is downloading files from web pages. Browserbase syncs every downloaded file to cloud storage and appends a Unix timestamp to avoid naming conflicts (e.g., `sample.pdf` becomes `sample-1719265797164.pdf`).
View or run the example template [here](https://www.browserbase.com/templates/download-financial-statements)
## Triggering downloads
First, trigger a download in your browser automation:
1. [Create a browser session](/platform/browser/getting-started/create-browser-session) and get the session ID
2. [Connect to the session](/platform/browser/getting-started/using-browser-session) using your preferred framework
3. Configure your library's downloads location
4. Perform the download action in your automation script
```typescript Playwright theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
// Required to avoid playwright overriding location
const client = await defaultContext.newCDPSession(page);
await client.send("Browser.setDownloadBehavior", {
behavior: "allow",
downloadPath: "downloads",
eventsEnabled: true,
});
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
const [download] = await Promise.all([
page.waitForEvent("download"),
page.locator("#download").click(),
]);
let downloadError = await download.failure();
if (downloadError !== null) {
console.log("Error happened on download:", downloadError);
throw new Error(downloadError);
}
// Store the session ID to retrieve downloads later
console.log("Download completed. Session ID:", session.id);
await page.close();
await browser.close();
})().catch((error) => console.error(error.message));
```
```typescript Puppeteer theme={null}
import puppeteer from "puppeteer-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const pages = await browser.pages();
const page = pages[0];
const client = await page.createCDPSession();
await client.send("Browser.setDownloadBehavior", {
behavior: "allow",
downloadPath: "downloads",
eventsEnabled: true,
});
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
const [downloadPath] = await Promise.all([
// Puppeteer doesn't have a download event, so we need to use the CDP session to listen for the download progress
new Promise(resolve => {
client.on('Browser.downloadProgress', event => {
if (event.state === 'completed') {
resolve(event.guid);
}
});
}),
page.locator("#download").click(),
]);
// Store the session ID to retrieve downloads later
console.log("Download completed. Session ID:", session.id);
await page.close();
await browser.close();
})().catch((error) => console.error(error.message));
```
```typescript Selenium theme={null}
import http from "http";
import { Builder, By } from "selenium-webdriver";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
// Create a custom connection with the session details
const customHttpAgent = new http.Agent({});
(customHttpAgent as any).addRequest = (req: any, options: any) => {
req.setHeader("x-bb-signing-key", session.signingKey);
(http.Agent.prototype as any).addRequest.call(customHttpAgent, req, options);
};
const driver = new Builder()
.forBrowser("chrome")
.usingHttpAgent(customHttpAgent)
.usingServer(
session.seleniumRemoteUrl
)
.build();
// Head to the test downloads page
await driver.get("https://browser-tests-alpha.vercel.app/api/download-test");
// Click the download button
console.log("Downloading file to the remote browser...");
await driver.findElement(By.id("download")).click();
// Store the session ID to retrieve downloads later
console.log("Download completed. Session ID:", session.id);
// Clean up
await driver.quit();
})().catch((error) => console.error(error.message));
```
```python Playwright theme={null}
from playwright.sync_api import sync_playwright
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
print(f"Session ID: {session.id}")
with sync_playwright() as playwright: # Use the session ID to connect to the browser
browser = playwright.chromium.connect_over_cdp(session.connect_url)
# Required to avoid playwright overriding the location
cdp_session = browser.new_browser_cdp_session()
cdp_session.send(
"Browser.setDownloadBehavior",
{
"behavior": "allow",
"downloadPath": "downloads",
"eventsEnabled": True,
},
)
context = browser.contexts[0]
page = context.pages[0]
# Head to the test downloads page
page.goto("https://browser-tests-alpha.vercel.app/api/download-test")
# Click the download button
print("Downloading file to the remote browser...")
page.get_by_role("link", name="Download File").click()
# Clean up
page.close()
browser.close()
print("Download completed. Use the session ID to retrieve the files.")
```
```python Selenium theme={null}
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.remote_connection import RemoteConnection
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
print(f"Session ID: {session.id}")
# Create a custom connection with the session details
class CustomRemoteConnection(RemoteConnection):
def get_remote_connection_headers(self, parsed_url, keep_alive=False):
headers = super().get_remote_connection_headers(parsed_url, keep_alive)
headers["x-bb-api-key"] = os.environ["BROWSERBASE_API_KEY"]
headers["session-id"] = session.id
return headers
custom_conn = CustomRemoteConnection(session.selenium_remote_url)
driver = webdriver.Remote(
command_executor=custom_conn,
options=webdriver.ChromeOptions()
)
# Head to the test downloads page
driver.get("https://browser-tests-alpha.vercel.app/api/download-test")
# Click the download button
print("Downloading file to the remote browser...")
driver.find_element(By.ID, "download").click()
# Clean up
driver.quit()
print("Download completed. Use the session ID to retrieve the files.")
```
**Critical: `setDownloadBehavior` Configuration**
When using Playwright or Puppeteer, you must call `Browser.setDownloadBehavior` via CDP to ensure downloads are synced to Browserbase's storage. Pay special attention to the `downloadPath` parameter. It must be set to `"downloads"` exactly as shown in the examples above.
Common misconfiguration issues:
* Using an absolute path (e.g., `/tmp/downloads`) instead of `"downloads"`
* Omitting the `setDownloadBehavior` call entirely
* Setting `behavior` to something other than `"allow"`
Without proper configuration, your downloads won't be available for retrieval.
Opening a PDF URL in a browser session also triggers a download to Browserbase's cloud storage. To view the PDF instead of downloading it, configure your browser settings as shown [here](/platform/browser/files/screenshots#view-a-pdf-in-browser).
## Retrieving downloaded files
After triggering downloads, retrieve them using the [Downloads API](/reference/api/list-downloads). The API provides granular access to individual downloaded files. You can list, filter, retrieve, and delete downloads.
Filenames don't include the timestamp suffix Browserbase adds during storage.
Files sync in real time, but large downloads may not be immediately available
through the `/downloads` endpoint. The code below includes retry logic to
handle this.
### List downloads
List all downloads for a session with optional filtering by filename, MIME type, file size, and creation time.
```typescript Node theme={null}
import { writeFileSync } from "node:fs";
const API_KEY = process.env.BROWSERBASE_API_KEY!;
async function saveDownloadsOnDisk(sessionId: string, retryForSeconds: number) {
const endTime = Date.now() + retryForSeconds * 1000;
while (Date.now() < endTime) {
try {
// List individual downloads for the session
const listResponse = await fetch(
`https://api.browserbase.com/v1/downloads?sessionId=${sessionId}`,
{ headers: { "x-bb-api-key": API_KEY } }
);
const { downloads, total } = await listResponse.json();
if (total > 0) {
console.log(`Found ${total} download(s)`);
for (const download of downloads) {
// Download each file individually
const fileResponse = await fetch(
`https://api.browserbase.com/v1/downloads/${download.id}`,
{
headers: {
"x-bb-api-key": API_KEY,
Accept: "application/octet-stream",
},
}
);
const buffer = Buffer.from(await fileResponse.arrayBuffer());
writeFileSync(download.filename, buffer);
console.log(`Saved: ${download.filename} (${download.size} bytes)`);
}
return;
}
} catch (e) {
console.error("Error fetching downloads:", e);
throw e;
}
// Wait 2 seconds before retrying
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error("No downloads found within the retry period");
}
(async () => {
// Use the session ID from your browser automation to retrieve downloads
const sessionId = "your-session-id";
await saveDownloadsOnDisk(sessionId, 20); // wait up to 20s
console.log("Downloads complete");
})().catch((error) => {
console.error("Download failed:", error);
});
```
```python Python theme={null}
import os
import time
import requests
API_KEY = os.environ["BROWSERBASE_API_KEY"]
def save_downloads_on_disk(session_id: str, retry_seconds: int = 20):
"""
List and download individual files from a session.
Retries for the specified number of seconds if no downloads are found.
:param session_id: The session ID from your browser automation
:param retry_seconds: How long to retry if no downloads are found
"""
end_time = time.time() + retry_seconds
while time.time() < end_time:
try:
# List individual downloads for the session
list_response = requests.get(
"https://api.browserbase.com/v1/downloads",
params={"sessionId": session_id},
headers={"x-bb-api-key": API_KEY},
)
data = list_response.json()
if data["total"] > 0:
print(f"Found {data['total']} download(s)")
for download in data["downloads"]:
# Download each file individually
file_response = requests.get(
f"https://api.browserbase.com/v1/downloads/{download['id']}",
headers={
"x-bb-api-key": API_KEY,
"Accept": "application/octet-stream",
},
)
with open(download["filename"], "wb") as f:
f.write(file_response.content)
print(f"Saved: {download['filename']} ({download['size']} bytes)")
return
except Exception as e:
print(f"Error fetching downloads: {e}")
raise
time.sleep(2) # Wait 2 seconds before retrying
raise TimeoutError("No downloads found within the retry period")
# Use the session ID from your browser automation to retrieve downloads
session_id = "your-session-id"
try:
save_downloads_on_disk(session_id)
print("Downloads complete")
except Exception as e:
print(f"Failed to retrieve downloads: {e}")
```
### Filtering options
| Parameter | Type | Description |
| --------------- | ------ | ---------------------------------------------------------- |
| `sessionId` | string | **Required.** The session ID to list downloads for. |
| `filename` | string | Filter by exact filename match. |
| `mimeType` | string | Filter by MIME type (e.g., `application/pdf`). |
| `minSize` | number | Minimum file size in bytes. |
| `maxSize` | number | Maximum file size in bytes. |
| `createdAfter` | string | Filter downloads created after this timestamp (ISO 8601). |
| `createdBefore` | string | Filter downloads created before this timestamp (ISO 8601). |
| `limit` | number | Maximum results to return (1-100, default: 20). |
| `offset` | number | Number of results to skip for pagination. |
**Example with filters:**
```typescript Node theme={null}
const API_KEY = process.env.BROWSERBASE_API_KEY!;
const sessionId = "your-session-id";
// Find PDF files larger than 1KB created in the last hour
const params = new URLSearchParams({
sessionId,
mimeType: "application/pdf",
minSize: "1024",
createdAfter: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
limit: "10",
});
const response = await fetch(
`https://api.browserbase.com/v1/downloads?${params}`,
{
headers: { "x-bb-api-key": API_KEY },
}
);
const { downloads } = await response.json();
console.log(`Found ${downloads.length} matching PDFs`);
```
```python Python theme={null}
import os
from datetime import datetime, timedelta
import requests
API_KEY = os.environ["BROWSERBASE_API_KEY"]
session_id = "your-session-id"
# Find PDF files larger than 1KB created in the last hour
response = requests.get(
"https://api.browserbase.com/v1/downloads",
params={
"sessionId": session_id,
"mimeType": "application/pdf",
"minSize": 1024,
"createdAfter": (datetime.now() - timedelta(hours=1)).isoformat(),
"limit": 10,
},
headers={"x-bb-api-key": API_KEY}
)
downloads = response.json()["downloads"]
print(f"Found {len(downloads)} matching PDFs")
```
### Get a download
Retrieve metadata or file content for a specific download. Use `Accept: application/json` for metadata, or `Accept: application/octet-stream` to download the file (default if no Accept header is provided).
```typescript Node theme={null}
import { writeFileSync } from "node:fs";
const API_KEY = process.env.BROWSERBASE_API_KEY!;
const downloadId = "download-uuid";
// Get metadata
const metadataResponse = await fetch(
`https://api.browserbase.com/v1/downloads/${downloadId}`,
{
headers: {
"x-bb-api-key": API_KEY,
Accept: "application/json",
},
}
);
const metadata = await metadataResponse.json();
console.log(`File: ${metadata.filename} (${metadata.size} bytes)`);
// Download file content
const fileResponse = await fetch(
`https://api.browserbase.com/v1/downloads/${downloadId}`,
{
headers: {
"x-bb-api-key": API_KEY,
Accept: "application/octet-stream",
},
}
);
const buffer = Buffer.from(await fileResponse.arrayBuffer());
writeFileSync(metadata.filename, buffer);
```
```python Python theme={null}
import os
import requests
API_KEY = os.environ["BROWSERBASE_API_KEY"]
download_id = "download-uuid"
# Get metadata
metadata_response = requests.get(
f"https://api.browserbase.com/v1/downloads/{download_id}",
headers={
"x-bb-api-key": API_KEY,
"Accept": "application/json"
}
)
metadata = metadata_response.json()
print(f"File: {metadata['filename']} ({metadata['size']} bytes)")
# Download file content
file_response = requests.get(
f"https://api.browserbase.com/v1/downloads/{download_id}",
headers={
"x-bb-api-key": API_KEY,
"Accept": "application/octet-stream"
}
)
with open(metadata["filename"], "wb") as f:
f.write(file_response.content)
```
### Delete a download
Remove a download from storage. Returns `204 No Content` on success.
```typescript Node theme={null}
const API_KEY = process.env.BROWSERBASE_API_KEY!;
const downloadId = "download-uuid";
const response = await fetch(
`https://api.browserbase.com/v1/downloads/${downloadId}`,
{
method: "DELETE",
headers: { "x-bb-api-key": API_KEY },
}
);
if (response.status === 204) {
console.log("Download deleted");
}
```
```python Python theme={null}
import os
import requests
API_KEY = os.environ["BROWSERBASE_API_KEY"]
download_id = "download-uuid"
response = requests.delete(
f"https://api.browserbase.com/v1/downloads/{download_id}",
headers={"x-bb-api-key": API_KEY}
)
if response.status_code == 204:
print("Download deleted")
```
### Download object
Each download contains the following fields:
| Field | Type | Description |
| ----------- | ------ | -------------------------------------------------- |
| `id` | string | Unique identifier for the download. |
| `sessionId` | string | The session ID this download belongs to. |
| `filename` | string | The filename of the downloaded file. |
| `mimeType` | string | The MIME type of the file. |
| `size` | number | File size in bytes. |
| `checksum` | string | SHA256 checksum of the file. |
| `createdAt` | string | Timestamp when the file was downloaded (ISO 8601). |
List and filter downloads
Get metadata or file content
Remove a download
# Files & screenshots
Source: https://docs.browserbase.com/platform/browser/files/overview
Download, upload, and capture files from your browser sessions
Trigger file downloads and retrieve them from your browser sessions programmatically.
Upload local files into browser sessions to interact with file input elements.
Capture full-page or element-level screenshots from your browser sessions.
Generate PDFs from any web page rendered in your browser session.
## When to use each
| Feature | Use when |
| ------------------------------------------------------ | ----------------------------------------------------------------------------- |
| [**Downloads**](/platform/browser/files/downloads) | You need to retrieve a file the browser downloaded (PDFs, CSVs, ZIPs, etc.) |
| [**Uploads**](/platform/browser/files/uploads) | Your automation needs to interact with a file input field |
| [**Screenshots**](/platform/browser/files/screenshots) | You want a visual snapshot of a page or element for debugging or verification |
| [**PDFs**](/platform/browser/files/pdfs) | You want to generate, download, or display a PDF from a web page |
## How files are stored
Downloaded files are synced to Browserbase's cloud storage automatically. You don't need to configure a download path. Files are available via the API after the session completes.
Screenshots and PDFs are returned directly in your automation code and saved locally, not stored in Browserbase cloud storage.
Downloaded files are renamed with a Unix timestamp to avoid conflicts (e.g. `report.pdf` → `report-1719265797164.pdf`).
# PDFs
Source: https://docs.browserbase.com/platform/browser/files/pdfs
With Browserbase, you can work with PDFs in a few ways:
1. **Generate**: Create PDFs from web pages with Playwright's `page.pdf()` method.
2. **Download**: Auto-download PDFs to Browserbase's cloud storage by opening a PDF URL. To retrieve them, see the [Downloads documentation](/platform/browser/files/downloads).
3. **View**: Display PDFs in the browser, instead of downloading them, by setting the `enablePdfViewer` property in your browser settings.
## Generate PDFs
After [creating and connecting to a session](/platform/browser/getting-started/using-browser-session), here's how to generate a PDF from a web page using Playwright:
```typescript Playwright theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
console.log("Starting remote browser...");
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await page.goto("https://news.ycombinator.com");
console.log("Generating PDF...");
await page.pdf({
path: "webpage.pdf",
format: "A4",
});
console.log("Shutting down...");
await page.close();
await browser.close();
})().catch((error) => {
console.error(error);
});
```
## Download PDFs
When you navigate to a PDF URL, Browserbase automatically downloads the PDF and cancels the navigation. Browserbase stores the file in cloud storage for later retrieval.
```typescript Playwright theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
console.log("Starting remote browser...");
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
// Configure download behavior
const client = await defaultContext.newCDPSession(page);
await client.send("Browser.setDownloadBehavior", {
behavior: "allow",
downloadPath: "downloads",
eventsEnabled: true,
});
// Navigate to PDF and trigger download
console.log("Attempting to download PDF...");
const [download] = await Promise.all([
page.waitForEvent("download"),
page.goto("https://constitutioncenter.org/media/files/constitution.pdf").catch(() => {
console.log("Navigation cancelled due to download (expected behavior)");
})
]);
let downloadError = await download.failure();
if (downloadError !== null) {
console.log("Error happened on download:", downloadError);
throw new Error(downloadError);
}
console.log("PDF download completed successfully");
console.log("Shutting down...");
await page.close();
await browser.close();
})().catch((error) => {
console.error(error);
});
```
```typescript Puppeteer theme={null}
import puppeteer from "puppeteer-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
console.log("Starting remote browser...");
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const pages = await browser.pages();
const page = pages[0];
// Configure download behavior
const client = await page.createCDPSession();
await client.send("Browser.setDownloadBehavior", {
behavior: "allow",
downloadPath: "downloads",
eventsEnabled: true,
});
console.log("Attempting to download PDF...");
// Navigate to PDF URL
await page.goto("https://constitutioncenter.org/media/files/constitution.pdf").catch(() => {
console.log("Navigation cancelled due to download (expected behavior)");
});
// Wait a moment for download to complete
await new Promise(resolve => setTimeout(resolve, 3000));
console.log("PDF download completed successfully");
console.log("Shutting down...");
await page.close();
await browser.close();
})().catch((error) => {
console.error(error);
});
```
```python Playwright theme={null}
from playwright.sync_api import sync_playwright, Playwright
import os
from browserbase import Browserbase
def main(p: Playwright):
print("Starting remote browser...")
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
browser = p.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
# Configure download behavior
client = context.new_cdp_session(page)
client.send("Browser.setDownloadBehavior", {
"behavior": "allow",
"downloadPath": "downloads",
"eventsEnabled": True,
})
print("Attempting to download PDF...")
# Set up download handling and navigate to PDF
with page.expect_download() as download_info:
try:
page.goto("https://constitutioncenter.org/media/files/constitution.pdf")
except Exception:
print("Navigation cancelled due to download (expected behavior)")
download = download_info.value
# Check for download errors
if download.failure():
print(f"Error happened on download: {download.failure()}")
raise Exception(download.failure())
print("PDF download completed successfully")
print("Shutting down...")
page.close()
browser.close()
with sync_playwright() as playwright:
main(playwright)
```
```python Selenium theme={null}
from selenium import webdriver
from selenium.webdriver.remote.remote_connection import RemoteConnection
import os
import time
from browserbase import Browserbase
def run():
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
# Create a custom connection with the session details
class CustomRemoteConnection(RemoteConnection):
def get_remote_connection_headers(self, parsed_url, keep_alive=False):
headers = super().get_remote_connection_headers(parsed_url, keep_alive)
headers["x-bb-api-key"] = os.environ["BROWSERBASE_API_KEY"]
headers["session-id"] = session.id
return headers
custom_conn = CustomRemoteConnection(session.selenium_remote_url)
# Configure Chrome options for downloads
options = webdriver.ChromeOptions()
prefs = {
"download.default_directory": "downloads",
"download.prompt_for_download": False,
"plugins.always_open_pdf_externally": True
}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Remote(custom_conn, options=options)
print("Attempting to download PDF...")
# Navigate to PDF URL
driver.get("https://constitutioncenter.org/media/files/constitution.pdf")
# Wait for download to complete
time.sleep(5)
print("PDF download completed successfully")
# Make sure to quit the driver so your session is ended!
driver.quit()
run()
```
Retrieve the downloaded PDF from Browserbase's cloud storage. See [Downloads](/platform/browser/files/downloads) for details on accessing your files.
View or run the example template [here](https://www.browserbase.com/templates/download-financial-statements)
## View PDFs
To view a PDF in the browser tab instead of downloading it, set `enablePdfViewer` to `true`:
```ts SDK theme={null}
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create({
browserSettings: {
enablePdfViewer: true,
},
});
```
```python SDK theme={null}
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
browser_settings={
"enablePdfViewer": True,
},
)
```
# Screenshots
Source: https://docs.browserbase.com/platform/browser/files/screenshots
Browserbase supports viewport and full-page screenshots with any browser automation framework. For best performance, use [CDP (Chrome DevTools Protocol)](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot) to capture screenshots; it's significantly faster than standard approaches.
## Take a screenshot
First, [create a browser session](/platform/browser/getting-started/create-browser-session) and [connect to it](/platform/browser/getting-started/using-browser-session). Then take a screenshot using CDP for best performance:
```typescript Playwright theme={null}
import { writeFileSync } from "fs";
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
console.log("Starting remote browser...");
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await page.goto("https://www.nationalgeographic.com/");
console.log("Taking a screenshot using CDP...");
// Create a CDP session for faster screenshots
const client = await defaultContext.newCDPSession(page);
// Capture a full-page screenshot using CDP
const { data } = await client.send("Page.captureScreenshot", {
format: "jpeg",
quality: 80,
captureBeyondViewport: true,
});
// Convert base64 to buffer and save
const buffer = Buffer.from(data, "base64");
writeFileSync("screenshot.jpeg", buffer);
console.log("Shutting down...");
await page.close();
await browser.close();
})().catch((error) => {
console.error(error);
});
```
```typescript Puppeteer theme={null}
import puppeteer from "puppeteer-core";
import fs from "fs";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
console.log("Starting remote browser...");
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const pages = await browser.pages();
const page = pages[0];
await page.goto("https://www.nationalgeographic.com/");
console.log("Taking a screenshot using CDP...");
// Create a CDP session for faster screenshots
const client = await page.createCDPSession();
// Capture a full-page screenshot using CDP
const { data } = await client.send("Page.captureScreenshot", {
format: "jpeg",
quality: 80,
captureBeyondViewport: true,
});
// Convert base64 to buffer and save
const buffer = Buffer.from(data, "base64");
fs.writeFileSync("screenshot.jpeg", buffer);
console.log("Shutting down...");
await browser.close();
})().catch((error) => {
console.error(error);
});
```
```python Playwright theme={null}
from playwright.sync_api import sync_playwright, Playwright
import os
import base64
from browserbase import Browserbase
def main(p: Playwright):
print("Starting remote browser...")
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
browser = p.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
page.goto("https://www.nationalgeographic.com/")
print("Taking a screenshot using CDP!")
# Create a CDP session for faster screenshots
client = context.new_cdp_session(page)
# Capture a full-page screenshot using CDP
screenshot_data = client.send("Page.captureScreenshot", {
"format": "jpeg",
"quality": 80,
"captureBeyondViewport": True
})
# Convert base64 to bytes and save
image_data = base64.b64decode(screenshot_data['data'])
with open('screenshot.jpeg', 'wb') as f:
f.write(image_data)
print("Shutting down...")
browser.close()
with sync_playwright() as playwright:
main(playwright)
```
```python Selenium theme={null}
from selenium import webdriver
from selenium.webdriver.remote.remote_connection import RemoteConnection
import os
from browserbase import Browserbase
def run():
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
# Create a custom connection with the session details
class CustomRemoteConnection(RemoteConnection):
def get_remote_connection_headers(self, parsed_url, keep_alive=False):
headers = super().get_remote_connection_headers(parsed_url, keep_alive)
headers["x-bb-api-key"] = os.environ["BROWSERBASE_API_KEY"]
headers["session-id"] = session.id
return headers
custom_conn = CustomRemoteConnection(session.selenium_remote_url)
options = webdriver.ChromeOptions()
driver = webdriver.Remote(custom_conn, options=options)
driver.get("https://www.nationalgeographic.com/")
print("Taking a screenshot...")
# Take screenshot using Selenium's built-in method
driver.save_screenshot('screenshot.png')
# Make sure to quit the driver so your session is ended!
driver.quit()
run()
```
For a **viewport-only screenshot** (just what's visible on screen) with CDP, remove `captureBeyondViewport` or set it to `false`.
### CDP screenshot reference
Just the CDP screenshot code:
```typescript Playwright theme={null}
// Create CDP session
const client = await defaultContext.newCDPSession(page);
// Capture screenshot
const { data } = await client.send("Page.captureScreenshot", {
format: "jpeg",
quality: 80,
captureBeyondViewport: true,
});
// Save
const buffer = Buffer.from(data, "base64");
writeFileSync("screenshot.jpeg", buffer);
```
```typescript Puppeteer theme={null}
// Create CDP session
const client = await page.createCDPSession();
// Capture screenshot
const { data } = await client.send("Page.captureScreenshot", {
format: "jpeg",
quality: 80,
captureBeyondViewport: true,
});
// Save
const buffer = Buffer.from(data, "base64");
fs.writeFileSync("screenshot.jpeg", buffer);
```
```python Playwright theme={null}
# Create CDP session
client = context.new_cdp_session(page)
# Capture screenshot
screenshot_data = client.send("Page.captureScreenshot", {
"format": "jpeg",
"quality": 80,
"captureBeyondViewport": True
})
# Save
image_data = base64.b64decode(screenshot_data['data'])
with open('screenshot.jpeg', 'wb') as f:
f.write(image_data)
```
## Why use CDP for screenshots?
[CDP (Chrome DevTools Protocol)](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot) offers several advantages for screenshots:
1. **Performance**: CDP screenshots are significantly faster than traditional methods
2. **Memory efficiency**: Uses less memory as it directly communicates with the browser's debugging protocol
3. **Quality control**: Provides more control over image quality and format settings
4. **Reliability**: More stable for full-page screenshots of complex web applications
For most use cases, the CDP approach shown above outperforms standard screenshot methods.
# Uploads
Source: https://docs.browserbase.com/platform/browser/files/uploads
Upload files to websites using Playwright, Puppeteer, or Selenium. The approach varies by framework:
## Playwright
### Direct upload
With Playwright, upload files directly from your local path. After [creating and connecting to a session](/platform/browser/getting-started/using-browser-session), follow these steps:
1. Make sure your file is available where you're running your Playwright code
2. Use the `setInputFiles` method to upload the file
3. The file path should be relative to your current working directory
```typescript Playwright theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
(async () => {
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
await page.goto("https://browser-tests-alpha.vercel.app/api/upload-test");
const fileInput = page.locator("#fileUpload");
// logo.png is available relative to the current working directory
await fileInput.setInputFiles("logo.png");
})().catch((error) => console.error(error));
```
```Python Playwright theme={null}
from playwright.sync_api import sync_playwright
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as playwright:
browser = playwright.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
page.goto("https://browser-tests-alpha.vercel.app/api/upload-test")
file_input = page.locator("#fileUpload")
file_input.set_input_files("logo.png")
```
### Large file uploads
For larger files, use the [Session Uploads API](/reference/api/create-session-uploads):
```typescript Playwright theme={null}
// IMPORTANT: Set your file name below
const fileName = "YOUR_FILE_NAME.EXAMPLE";
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
import * as fs from "fs";
const apiKey = process.env.BROWSERBASE_API_KEY!;
async function upload() {
// 1. Initialize Browserbase Client
console.log("Initializing Browserbase client");
const bb = new Browserbase({ apiKey });
// 2. Create Browser Session
console.log("Creating new browser session");
const session = await bb.sessions.create();
// 3. Upload file via the Uploads API
console.log("Uploading file");
try {
const fileStream = fs.createReadStream(fileName);
const result = await bb.sessions.uploads.create(session.id, {
file: fileStream,
});
console.log(`Upload successful: ${JSON.stringify(result)}`);
} catch (error) {
console.error(`Upload failed... exiting: ${error}`);
return;
}
// 4. Connect to the Session
console.log("Connecting to browser session");
const browser = await chromium.connectOverCDP(session.connectUrl);
const defaultContext = browser.contexts()[0];
const page = defaultContext.pages()[0];
// 5. Get Live View link for remote debugging
const liveViews = await bb.sessions.debug(session.id);
console.log("Live View link:", liveViews.debuggerUrl);
// 6. Use the Browser
console.log("Navigating to page: upload-test");
await page.goto("https://browser-tests-alpha.vercel.app/api/upload-test", {
waitUntil: "domcontentloaded",
});
// Set up CDP client for additional controls
const cdpSession = await defaultContext.newCDPSession(page);
const root = await cdpSession.send("DOM.getDocument");
// Find the input element
const inputNode = await cdpSession.send("DOM.querySelector", {
nodeId: root.root.nodeId,
selector: "#fileUpload",
});
// Use DOM.setFileInputFiles CDP command
const remoteFilePath = `/tmp/.uploads/${fileName}`;
await cdpSession.send("DOM.setFileInputFiles", {
files: [remoteFilePath],
nodeId: inputNode.nodeId,
});
console.log("Waiting for 60 seconds: allow time for 1) file upload and 2) to see the file upload...");
await new Promise((resolve) => setTimeout(resolve, 60000));
// 7. Cleanup
console.log("Closing browser session");
await page.close();
await browser.close();
// 8. Session Recording Link
console.log(`
Your session dashboard is ready:
https://www.browserbase.com/sessions/${session.id}`);
}
// Execute the main function
upload().catch((error) => console.error(error));
```
```python Playwright theme={null}
# IMPORTANT: Set your file name below
file_name = "YOUR_FILE_NAME.EXAMPLE"
import os
from browserbase import Browserbase
from playwright.sync_api import sync_playwright
import time
api_key = os.environ.get("BROWSERBASE_API_KEY")
def upload(playwright):
# 1. Initialize Browserbase Client
print("Initializing Browserbase client")
bb = Browserbase(api_key=api_key)
# 2. Create Browser Session
print("Creating new browser session")
session = bb.sessions.create()
# 3. Upload file via the Uploads API: https://docs.browserbase.com/reference/api/create-session-uploads
print("Uploading file")
try:
with open(file_name, 'rb') as file:
result = bb.sessions.uploads.create(
id=session.id,
file=file
)
print(f"Upload successful: {result}")
except Exception as e:
print(f"Upload failed... exiting: {str(e)}")
return
# 4. Connect to the Session
print("Connecting to browser session")
browser = playwright.chromium.connect_over_cdp(session.connect_url)
default_context = browser.contexts[0]
page = default_context.pages[0]
# 5. Get Live View link for remote debugging
live_views = bb.sessions.debug(session.id)
print("Live View link:", live_views.debugger_url)
# 6. Use the Browser
print("Navigating to page: upload-test")
page.goto("https://browser-tests-alpha.vercel.app/api/upload-test", wait_until="domcontentloaded")
# Set up CDP client for additional controls
cdp_session = default_context.new_cdp_session(page)
root = cdp_session.send('DOM.getDocument')
# Find the input element
input_node = cdp_session.send('DOM.querySelector', {
'nodeId': root['root']['nodeId'],
'selector': '#fileUpload'
})
# Use DOM.setFileInputFiles CDP command with the found nodeId and set the remote file path
remote_file_path = f"/tmp/.uploads/{file_name}";
cdp_session.send('DOM.setFileInputFiles', {
'files': [remote_file_path],
'nodeId': input_node['nodeId']
})
print("Waiting for 60 seconds: allow time for 1) file upload and 2) to see the file upload...")
time.sleep(60)
# 7. Cleanup
print("Closing browser session")
page.close()
browser.close()
# 8. Session Recording Link
print(f"""
Your session dashboard is ready:
https://www.browserbase.com/sessions/{session.id}""")
# Execute the main function
if __name__ == "__main__":
with sync_playwright() as playwright:
upload(playwright)
```
### Manual upload through Live View
When using [Live View](/platform/browser/observability/session-live-view), clicking a file input triggers the browser's native file picker. Since the browser runs remotely on Browserbase infrastructure, it can't access files on your local machine.
To handle manual uploads through Live View, intercept file chooser events and upload programmatically:
1. Detect file input clicks using Playwright's `page.on('filechooser')` event
2. Show your own file picker (e.g., a native OS picker or web UI) to select a local file
3. Upload the file via the Session Uploads API
4. Attach the file to the input element using CDP's `DOM.setFileInputFiles`
See the following [example script](https://github.com/browserbase/playbook/blob/main/node/playwright/_tools/uploads/live-view-upload.ts) for a complete implementation.
Learn more about the available params and response fields
# Create a browser session
Source: https://docs.browserbase.com/platform/browser/getting-started/create-browser-session
Learn how to create and configure browser sessions in Browserbase
A browser session represents a single browser instance running in the cloud. It's the fundamental building block of Browserbase, providing an isolated environment for your web automation tasks.
**Looking for serverless automation?** [Functions](/platform/runtime/overview) automatically create and manage sessions for you, letting you deploy browser automation as API-invokable functions without manual session management.
## Creating a session
Create browser sessions through the [Sessions API](/reference/api/create-a-session), which gives you full control over configuration and features. After creation, you'll receive a connection URL to use with your preferred automation framework.
The create session API is rate limited based on your plan's concurrent session
limits. See [Concurrency & Rate
Limits](/optimizations/concurrency/overview) for details on limits and
best practices for handling them.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create({
// Add configuration options here
});
```
```python theme={null}
import os
from browserbase import Browserbase
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
# Add configuration options here
)
```
```bash theme={null}
curl --request POST \
--url "https://api.browserbase.com/v1/sessions" \
--header "Content-Type: application/json" \
--header "x-bb-api-key: $BROWSERBASE_API_KEY" \
--data '{
// Add configuration options here
}'
```
## Configuration options
When creating a session, you can configure various settings. For complete API details, see:
* [Create Session API Reference](/reference/api/create-a-session)
* [Node.js SDK Reference](/reference/sdk/nodejs)
* [Python SDK Reference](/reference/sdk/python)
### Basic settings
* **Region**: Decrease latency by choosing where your browser runs using [browser regions](/optimizations/latency/multi-region)
* **Viewport** - Set custom screen dimensions for your browser window.
* **Keep alive**: Enable [longer-running sessions](/platform/browser/long-sessions/overview) that run even after disconnection
* **Recording** - Enable/disable [session recording](/platform/browser/observability/session-recording) (enabled by default)
* **Logging** - Enable/disable session logging for debugging (enabled by default)
### Advanced features
* **[Agent Identity](/platform/identity/overview)** - Configure browser identity:
* Automatic fingerprinting (devices, locales, operating systems)
* Verified (Scale plan only)
* [Proxy settings](/platform/identity/proxies)
* Captcha solving (enabled by default)
* [Allowed domains](/platform/identity/allowed-domains)
* **[Extensions](/platform/browser/core-features/browser-extensions)** - Load custom browser extensions to enhance functionality
* **[Browser Context](/platform/browser/core-features/contexts)** - Configure isolated browsing contexts for session persistence
* **[User Metadata](/platform/browser/core-features/session-metadata)** - Attach custom data for session organization and filtering
## Next steps
Once you've created a session, you can:
1. Connect to it using your preferred automation framework - see [Using a browser session](/platform/browser/getting-started/using-browser-session)
2. Monitor it through the [Session Inspector](/platform/browser/observability/observability)
3. End it manually or let it timeout - see [Manage a browser session](/platform/browser/getting-started/manage-browser-session)
## When to use Browser?
Use Browsers when the page requires interaction, data is behind a login or JS, or you need high accuracy over speed.
Think about [Search](/platform/search/overview) -> [Fetch](/platform/fetch/overview) -> [Browsers](/platform/browser/getting-started/create-browser-session)
* **[Search:](/platform/search/overview)** Find relevant sources (website, news, docs).
* **[Fetch:](/platform/fetch/overview)** Quickly extract content from most pages, and filter out low-value results.
* **[Browsers:](/platform/browser/getting-started/create-browser-session)** Log in to portals with [agent identity](/platform/identity/overview), navigate complex pages, and extract the hard-to-reach data.
Read our [full guide](https://www.browserbase.com/blog/search-vs-fetch-vs-browsers) breaking down which API to use with examples.
# Deploying a browser session
Source: https://docs.browserbase.com/platform/browser/getting-started/deploying-browser-session
Learn how to deploy existing browser automation scripts as Functions
Functions let you deploy your existing browser automation scripts to Browserbase's serverless infrastructure. Instead of running scripts on your own servers, publish them as cloud functions invocable via API.
This guide shows you how to take an existing automation script and deploy it as a Function.
Functions are currently only available in the **us-west-2** region.
## Prerequisites
Before deploying, ensure you have:
1. An existing TypeScript browser automation script using Stagehand, Playwright, or Puppeteer
2. A Browserbase account with your API key and Project ID (available in [Settings](https://www.browserbase.com/settings))
## Create Function scaffolding
Initialize a new Functions project. This creates the directory structure, installs dependencies, and sets up the required configuration files.
Run the following command to create a new Functions project:
```bash theme={null}
pnpm dlx @browserbasehq/sdk-functions init my-functions-project
```
This creates a `my-functions-project` directory with:
* A configured `package.json` with required dependencies
* A `tsconfig.json` for TypeScript support
* A template `.env` file for your credentials
* A starter `index.ts` file with an example Function
```bash theme={null}
cd my-functions-project
```
Open the `.env` file and add your Browserbase credentials:
```bash theme={null}
BROWSERBASE_PROJECT_ID=your_project_id
BROWSERBASE_PROJECT_ID=your_project_id
BROWSERBASE_API_KEY=your_api_key
```
Get your API key and Project ID from the [Browserbase Dashboard Settings](https://www.browserbase.com/settings).
## Adapting your script
The key difference between running locally and deploying as a Function is how you connect to the browser. In a Function, Browserbase automatically creates a session and provides the connection details through the `context` parameter.
### Using the Function context
Every Function receives a `context` object containing session information:
```typescript theme={null}
defineFn("my-function", async (context) => {
const { session } = context;
// session.id - The session ID
// session.connectUrl - CDP connection URL for Playwright/Puppeteer
});
```
## Framework integration
Choose your framework below to see how to adapt your existing script for deployment.
Stagehand is the recommended SDK for Functions. Use `env: "LOCAL"` mode and pass the CDP connection URL from `context.session.connectUrl` via `localBrowserLaunchOptions`.
**Local Script:**
```typescript theme={null}
import { Stagehand } from "@browserbasehq/stagehand";
const stagehand = new Stagehand({
env: "BROWSERBASE",
});
await stagehand.init();
await stagehand.page.goto("https://example.com");
// Your automation logic...
```
**Function:**
```typescript theme={null}
import { defineFn } from "@browserbasehq/sdk-functions";
import { Stagehand } from "@browserbasehq/stagehand";
defineFn("my-stagehand-function", async (context) => {
const { session } = context;
const stagehand = new Stagehand({
env: "LOCAL",
localBrowserLaunchOptions: {
cdpUrl: session.connectUrl,
},
});
await stagehand.init();
await stagehand.page.goto("https://example.com");
// Your automation logic...
return { success: true };
});
```
Using `env: "LOCAL"` with `localBrowserLaunchOptions.cdpUrl` connects Stagehand directly to the browser session via CDP. You can still use other Stagehand options alongside this configuration. See the [Stagehand documentation](https://docs.stagehand.dev/v3/references/stagehand) for all available configuration options.
Connect to the browser session using Playwright's CDP connection with the `session.connectUrl`.
**Local Script:**
```typescript theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const browserContext = browser.contexts()[0]!;
const page = browserContext.pages()[0]!;
await page.goto("https://example.com");
// Your automation logic...
```
**Function:**
```typescript theme={null}
import { defineFn } from "@browserbasehq/sdk-functions";
import { chromium } from "playwright-core";
defineFn("my-playwright-function", async (context) => {
const { session } = context;
const browser = await chromium.connectOverCDP(session.connectUrl);
const browserContext = browser.contexts()[0]!;
const page = browserContext.pages()[0]!;
await page.goto("https://example.com");
// Your automation logic...
return { success: true };
});
```
Connect to the browser session using Puppeteer's WebSocket endpoint with the `session.connectUrl`.
**Local Script:**
```typescript theme={null}
import puppeteer from "puppeteer-core";
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const page = (await browser.pages())[0]!;
await page.goto("https://example.com");
// Your automation logic...
```
**Function:**
```typescript theme={null}
import { defineFn } from "@browserbasehq/sdk-functions";
import puppeteer from "puppeteer-core";
defineFn("my-puppeteer-function", async (context) => {
const { session } = context;
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl,
});
const page = (await browser.pages())[0]!;
await page.goto("https://example.com");
// Your automation logic...
return { success: true };
});
```
## Testing locally
Before deploying, test your Function locally using the development server.
### Start the development server
```bash theme={null}
pnpm bb dev index.ts
```
The server starts on `http://127.0.0.1:14113` and watches for file changes, automatically reloading when you modify your Function files.
### Invoke your Function locally
Use curl to invoke your Function against the local development server:
```bash theme={null}
curl -X POST http://127.0.0.1:14113/v1/functions/my-function/invoke \
-H "Content-Type: application/json"
```
Replace `my-function` with the name you passed to `defineFn`.
### Pass parameters
If your Function accepts parameters, include them in the request body:
```bash theme={null}
curl -X POST http://127.0.0.1:14113/v1/functions/my-function/invoke \
-H "Content-Type: application/json" \
-d '{"params": {"url": "https://example.com"}}'
```
Pipe the output to [jq](https://github.com/jqlang/jq) for formatted JSON:
```bash theme={null}
curl -X POST http://127.0.0.1:14113/v1/functions/my-function/invoke \
-H "Content-Type: application/json" | jq
```
The local development server creates real Browserbase sessions using your credentials. This ensures your Function behaves the same locally and in production.
## Deploying to production
Once your Function works locally, deploy it to Browserbase:
```bash theme={null}
pnpm bb publish index.ts
```
You'll receive a Function ID that you can use to invoke your deployed Function:
```bash theme={null}
curl --request POST \
--url https://api.browserbase.com/v1/functions/YOUR_FUNCTION_ID/invoke \
--header 'Content-Type: application/json' \
--header 'x-bb-api-key: YOUR_API_KEY' \
--data '{"params": {}}'
```
## Learn more
For detailed information on passing parameters, best practices, and limitations, see the [Functions feature documentation](/platform/runtime/overview).
## Next steps
Learn about defining, publishing, and invoking Functions
Detailed API reference and configuration options
Learn about session creation options
# Manage a browser session
Source: https://docs.browserbase.com/platform/browser/getting-started/manage-browser-session
Learn how to manage session termination and inspect completed sessions
While Browserbase automatically handles session termination when you disconnect, understanding how sessions end helps you debug failed runs, manage long-running sessions, optimize resource usage, and investigate timeouts or errors.
## Session termination
Browser sessions can end in these ways:
1. **Automatic timeout**
Sessions have a default timeout configured at the project level, which can be customized when creating a session. For longer-running tasks, enable [keep alive](/platform/browser/long-sessions/overview).
2. **Manual termination**
You can end sessions explicitly by closing the browser programmatically (`browser.close()` or `driver.quit()`), using the Sessions API, or releasing keep-alive sessions when no longer needed.
3. **Unhandled errors**
Unhandled errors in your automation code can cause your script to disconnect from the browser, ending the session prematurely. Common scenarios include network interruptions, uncaught exceptions, or exceeded resource limits.
To prevent premature termination, make sure to implement proper error handling and cleanup in your code.
4. **CDP inactivity timeout**
CDP (Chrome DevTools Protocol) connections are closed after 10 minutes without any CDP commands. If your script holds a session open without sending CDP commands for this period, the connection will be terminated.
To keep the connection alive, send a periodic heartbeat, a lightweight CDP command at a regular interval:
```javascript Node.js theme={null}
// Send a heartbeat every 5 minutes to prevent CDP inactivity timeout
setInterval(async () => {
await page.evaluate(() => undefined);
}, 5 * 60 * 1000);
```
```python Python theme={null}
import asyncio
# Send a heartbeat every 5 minutes to prevent CDP inactivity timeout
async def heartbeat(page):
while True:
try:
await asyncio.sleep(5 * 60)
await page.evaluate("undefined")
except Exception:
break
asyncio.create_task(heartbeat(page))
```
## Session timeout settings
Configure timeouts at two levels:
**Project level**
Set the default timeout for all sessions in your [project settings](https://browserbase.com/settings). This acts as the fallback when no session-specific timeout is set.
**Session level**
Override the project timeout for specific sessions when [creating them](/platform/browser/getting-started/create-browser-session#configuration-options). This gives you fine-grained control over individual session durations.
## Debugging completed sessions
The [Session Inspector](/platform/browser/observability/observability) is your primary tool for analyzing completed sessions:
Record and replay browser activity to understand what happened
Inspect HTTP traffic, responses, and timing
Review JavaScript output and debug messages
Track CPU, memory usage, and other metrics
## Measuring usage
Track and analyze your browser session usage through multiple interfaces:
**Dashboard**
Your central hub at [browserbase.com/overview](https://browserbase.com/overview) shows total browser minutes, active sessions, usage trends, and billing information.
**Sessions list**
Browse your session history at [browserbase.com/sessions](https://browserbase.com/sessions) to view duration, status, and resource consumption for individual sessions.
For programmatic access to these metrics, see the [Measuring Usage Guide](/optimizations/cost/measuring-usage).
# Managed vs self-hosted
Source: https://docs.browserbase.com/platform/browser/getting-started/remote-browser-versus-local-browser
Why managed Browserbase sessions usually win over self-hosted Chromium in a VM.
Local Chromium in a VM can feel fast in the happy path. In production you inherit the full cost of operating a browser fleet: session lifecycle, pooling, memory leaks, crash recovery, concurrency, updates, observability, and security isolation.
This page explains that tradeoff for teams using [Skills](/integrations/skills/introduction) and other Browserbase integrations, and what you take on when you self-host.
## What you own when you DIY
When you run your own Chromium in VMs or containers, you own:
* **Session lifecycle**: Browsers leak memory; sessions hang. You need pooling, recycling, and sane timeouts.
* **Concurrency**: Parallel browser processes under load fail in ways that are hard to reproduce locally.
* **Crash recovery**: Silent failures at scale are dangerous; you need health checks, retries, and clear failure modes.
* **Debuggability**: Headless stacks and logs help; production debugging often needs more than a stack trace.
* **Agent Identity**: Fingerprinting, CAPTCHA flows, and partner-specific behavior take ongoing work, not a one-time flag.
* **Proxies and routing**: Geo, residential vs datacenter, rotation, and failure handling are their own product surface.
* **Chromium lifecycle**: Pinning versions, security updates, and rollout strategy across environments.
* **Security isolation**: Agents visit untrusted pages, so prompt injections and malicious content can turn the browser into an attack surface. Without per-session sandboxing, you risk data exfiltration, environment probing, and lateral movement inside your VM.
## What Browserbase gives you
Browserbase absorbs that operational surface so your team can focus on agent logic:
| DIY burden | Browserbase |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Session lifecycle & pooling | Managed sessions with clear APIs and Dashboard visibility |
| Crashes & concurrency | Browserbase tunes hosted infrastructure for parallel browser workloads |
| Debugging | [Live View](/platform/browser/observability/session-live-view) for real-time inspection; [session recordings](/platform/browser/observability/session-recording) for replay in the Dashboard |
| Agent Identity | [Verified](/platform/identity/verified-customization) and [Agent Identity](/platform/identity/overview) |
| Proxies | [Proxy configuration](/platform/identity/proxies) including geolocation and routing |
| Browser updates | Browserbase maintains purpose-built Chromium as part of the platform |
| Security isolation | Every session runs in its own ephemeral sandbox, so a compromised page can't reach your infrastructure or other sessions |
## Understanding the performance numbers
Raw timings depend on **what you measure**: create vs connect vs first navigation vs teardown, and whether you reuse the default context/page or create new ones.
Things that routinely move the needle vs a minimal local benchmark:
* **Network**: Remote sessions add RTT; local Chromium uses near-IPC latency.
* **Hardware**: Your laptop or CI runner may have more CPU/RAM than a generic hosted shape.
* **Features**: Session recording, screencast setup, [Verified](/platform/identity/verified-customization), and [proxies](/platform/identity/proxies) add real work; disable or align defaults when you want apples-to-apples comparisons.
For more tuning context, see [Speed optimization](/optimizations/latency/speed-optimization).
## Local vs managed at a glance
| Need | Local Chromium | Browserbase |
| --------------------------------------------- | :------------: | :---------: |
| Zero-latency inner-loop dev | ✅ | No |
| Air-gapped or policy-locked environments | ✅ | No |
| Reliability and crash recovery at scale | No | ✅ |
| Parallelism across many concurrent sessions | No | ✅ |
| Shared debugging (Live View, recordings) | No | ✅ |
| Agent Identity (fingerprints, CAPTCHAs, auth) | No | ✅ |
| Managed proxies and geo-routing | No | ✅ |
| Security isolation per session | No | ✅ |
| Chromium updates handled for you | No | ✅ |
Once you need reliability, parallelism, shared debugging, or Agent Identity across a team, managed sessions usually win on total cost.
## Further reading
Install and use Browserbase Skills with your agent
`browse` for browser automation, cloud APIs, Functions, and Browse.sh skills
Debug live sessions in real time
Video replay and observability in the Dashboard
Verified browsers, proxies, and authentication for agents
Geolocation, rotation, and routing for browser sessions
# Using a browser session
Source: https://docs.browserbase.com/platform/browser/getting-started/using-browser-session
Learn how to connect to and interact with browser sessions
## Connecting to a session
Once you [create a session](/platform/browser/getting-started/create-browser-session), you'll receive a connection URL to use with your preferred automation framework. Here's how to connect:
```javascript Stagehand theme={null}
import { Stagehand } from "@browserbasehq/stagehand";
const stagehand = new Stagehand({
env: "BROWSERBASE",
});
await stagehand.init();
// Get the default page
const page = stagehand.context.pages()[0];
```
```javascript Playwright theme={null}
import { chromium } from "playwright-core";
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
// Create a session
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
// Use the default context and page
const context = browser.contexts()[0];
const page = context.pages()[0];
```
```javascript Puppeteer theme={null}
import puppeteer from "puppeteer-core";
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
// Create a session
const session = await bb.sessions.create();
const browser = await puppeteer.connect({
browserWSEndpoint: session.connectUrl
});
// Use the default page
const page = (await browser.pages())[0];
```
```javascript Selenium theme={null}
import { Builder } from 'selenium-webdriver';
import { Options } from 'selenium-webdriver/chrome';
import { Browserbase } from "@browserbasehq/sdk";
class BrowserbaseConnection {
constructor(sessionId) {
this.sessionId = sessionId;
}
getHeaders() {
return {
'x-bb-api-key': process.env.BROWSERBASE_API_KEY,
'session-id': this.sessionId
};
}
}
// Create a session
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
const session = await bb.sessions.create();
// Create connection with custom headers
const connection = new BrowserbaseConnection(session.id);
// Configure WebDriver
const driver = await new Builder()
.usingServer(session.seleniumRemoteUrl)
.setChromeOptions(new Options())
.build();
// Add custom headers to all requests
const originalExecute = driver.executor_.execute.bind(driver.executor_);
driver.executor_.execute = async function (command) {
command.headers = { ...command.headers, ...connection.getHeaders() };
return originalExecute(command);
};
```
```python Stagehand theme={null}
import os
import asyncio
from stagehand import AsyncStagehand
async def main():
async with AsyncStagehand(
browserbase_api_key=os.environ.get("BROWSERBASE_API_KEY"),
browserbase_project_id=os.environ.get("BROWSERBASE_PROJECT_ID"),
model_api_key=os.environ.get("MODEL_API_KEY"),
) as client:
session = await client.sessions.start(
model_name="anthropic/claude-sonnet-4-6",
)
asyncio.run(main())
```
```python Playwright theme={null}
import os
from playwright.sync_api import sync_playwright
from browserbase import Browserbase
# Create a session
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as playwright:
browser = playwright.chromium.connect_over_cdp(session.connect_url)
# Use the default context and page
context = browser.contexts[0]
page = context.pages[0]
```
```python Selenium theme={null}
import os
from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.remote_connection import RemoteConnection
from browserbase import Browserbase
class BrowserbaseConnection(RemoteConnection):
"""Manage a single session with Browserbase."""
def __init__(self, session_id, *args, **kwargs):
self.session_id = session_id
super().__init__(*args, **kwargs)
def get_remote_connection_headers(self, parsed_url, keep_alive=False):
headers = super().get_remote_connection_headers(parsed_url, keep_alive)
headers.update({
"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"],
"session-id": self.session_id,
})
return headers
# Create a session
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
# Connect using custom connection class
connection = BrowserbaseConnection(
session.id,
session.selenium_remote_url
)
driver = webdriver.Remote(
command_executor=connection,
options=webdriver.ChromeOptions()
)
```
### Connection best practices
1. **Connection timeout**: You have 5 minutes to connect to a newly created session before it terminates. To prevent timeouts:
* Connect promptly after creation
* Enable [keep alive](/platform/browser/long-sessions/overview) for sessions that need to persist
* Use the connection URL immediately after receiving it
2. **Use default context**: Always use the default context and page when possible to ensure proper functionality of Verified features:
```typescript Stagehand theme={null}
// Stagehand manages the context: get the default page directly
const page = stagehand.context.pages()[0];
```
```typescript Playwright theme={null}
const context = browser.contexts()[0];
const page = context.pages()[0];
```
```typescript Puppeteer theme={null}
const page = (await browser.pages())[0];
```
```typescript Selenium theme={null}
// Uses default context automatically
```
```python Stagehand theme={null}
# Stagehand manages the session: use the session object directly
session = await client.sessions.start(model_name="anthropic/claude-sonnet-4-6")
```
```python Playwright theme={null}
context = browser.contexts[0]
page = context.pages[0]
```
```python Selenium theme={null}
# Uses default context automatically
```
## Controlling the browser
Once connected, use your preferred framework's APIs to control the browser. Each framework has its own methods for navigation, interaction, and automation.
Build reliable browser automation with AI-powered element selection and self-healing scripts
Create fast, reliable end-to-end tests with built-in auto-waiting and mobile
emulation
Headless Chrome automation with a lightweight API and strong DevTools
integration
Industry-standard testing framework supporting all major browsers and programming languages
### Browserbase features
When running browsers in the cloud, certain operations require special handling through the Browserbase APIs:
Securely retrieve files from your cloud browser session
Capture high-quality browser screenshots with custom settings
Create PDFs with advanced formatting options
Transfer files directly to your browser session
### Live view
Live view gives you real-time visibility into your browser sessions through two interfaces:
#### Session Inspector
The [Session Inspector](/platform/browser/observability/observability) provides real-time debugging:
Monitor your session's activity with:
* Live browser state and interactions
* Real-time network requests and responses
* Console output and error tracking
* Performance metrics and resource usage
* Session recording and replay
#### Embedded view
Integrate the [Live View](/platform/browser/observability/session-live-view) directly into your application to show your users their automated browser sessions in real-time. The Live View enables remote control over the browser, unlocking human-in-the-loop possibilities to handle authentication, CAPTCHAs, or unexpected errors.
## Ending your session
Browserbase automatically handles session termination when you disconnect. For details about termination, timeouts, and lifecycle management best practices, see [Manage a browser session](/platform/browser/getting-started/manage-browser-session).
# What is a headless browser?
Source: https://docs.browserbase.com/platform/browser/getting-started/what-is-headless-browser
A headless browser is a web browser controlled by code, without a GUI. Learn how headless browsers work and how to use them for automation, data extraction, and testing.
A headless browser is a web browser controlled by code that uses software like humans do - visiting sites, clicking buttons, and submitting forms, all automatically.
It's functionally identical to the web browser you use on your machine, but can run on a server. It's "headless" because there's no graphical user interface (GUI), but it still loads and interacts with pages exactly like a regular web browser does.
## How does it work?
If there's no GUI, how does code control a headless browser? The answer lies in browser protocols - standardized ways for code to communicate with and control browsers. The most widely used is the Chrome DevTools Protocol (CDP), which provides low-level control over browser operations.
These protocols are low-level, meaning they provide a lot of control over the browser, but they are also complex and difficult to work with. It's like building software using machine code.
That's why developers typically use browser automation frameworks - tools that provide simple, abstracted interfaces for controlling browsers.
## Why would I use a headless browser?
The real power of code is that it does work for people. Traditionally, that means connecting many APIs together, processing data, and presenting it in a beautiful interface. Code uses other code to get things done.
Over the last decade, software has undergone a massive shift from desktop to web applications. People now complete their work, handle their finances, and run their businesses through web browsers. As more daily tasks move online, the desire to automate web-based workflows becomes increasingly crucial.
But what if you want to automate something that doesn't expose an API? What if you need to interact with an application that hasn't been built to be used by code?
Simply fetching HTML data from the website often isn't enough. Modern websites use JavaScript to load data on the client side and control interactive elements - if you only retrieve the HTML, you miss crucial parts of how the application actually works.
Instead, what if you could use software the same way people do? Going to the website, clicking buttons, submitting forms, and performing all the actions a person would do. But instead of being controlled by a human, it's controlled by code.
This is the power of a headless browser.
## Browser automation frameworks
Browser automation frameworks make it easy to write code that controls browsers. They handle the complexity of protocol communication and provide familiar programming interfaces. Here are the most popular options:
[Playwright](https://playwright.dev/)
* Created by Microsoft
* Modern, promise-based API
* Originally focused on testing web applications
[Stagehand](https://stagehand.dev/)
* Created by Browserbase
* A superset of Playwright, with AI features for self-healing automations
* Specialized for browser automation
[Puppeteer](https://pptr.dev/)
* Developed by Google's Chrome team
* Deep integration with CDP
* Excellent for PDF generation and screenshots
[Selenium](https://www.selenium.dev/)
* Oldest automation framework
* Uses WebDriver protocol - a legacy protocol for browser automation
* Large ecosystem of tools and plugins
## Using headless browsers
Headless browsers unlock new possibilities for developers. By enabling code to interact with any website the same way people do, they allow you to build integrations without being limited by available APIs.
Getting started with headless browsers is straightforward. With just a few lines of code, you can automate basic web interactions locally:
```javascript theme={null}
const { chromium } = require("playwright");
async function example() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.click("#submit-button");
}
```
However, running headless browsers in production presents significant challenges. You need to:
* Manage browser instances and resources
* Handle concurrent sessions
* Deal with browser crashes and cleanup
* Scale infrastructure as demand grows
* Monitor performance and reliability
This is where [Browserbase](https://www.browserbase.com) comes in. It provides a reliable, scalable infrastructure for running headless browsers in production, letting you focus on building your automation logic rather than managing browser infrastructure.
Learn more about Browserbase.
This simple, step-by-step tutorial will help you get started with
Browserbase. Start your first cloud browser in no time.
# Keep alive
Source: https://docs.browserbase.com/platform/browser/long-sessions/keep-alive
Keep sessions alive across disconnects, allowing you to reconnect without ending your session.
The `keepAlive` feature lets you **keep sessions alive across disconnects**, so you can reconnect and continue using the same session.
| | When connection closes |
| ---------------------- | ---------------------------------------- |
| **Without keep alive** | Session ends |
| **With keep alive** | Session stays available for reconnection |
Session keep alive is only available on paid plans.
## Why use keep alive?
* **Reconnection workflows**: Connect, disconnect, and reconnect to the same session
* **Multiple connections**: Different scripts or tools can connect to the same session
* **Survive disconnects**: Network issues won't end your session
* **Performance**: Reconnecting to an existing session is faster than creating a new one
* **Session reuse**: Re-run scripts against the same session without recreating it
* **Billing optimization**: keep alive lets you [avoid minimum browser usage charges](/account/billing/plans) if running many short-lived sessions (under 1 minute)
## Using keep alive
### Creating a keep alive session
Setting `keepAlive` to `true` keeps the session available for later use. Reconnect to the keep alive session using the same connect URL.
```typescript SDK theme={null}
import Browserbase from "browserbase";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create({
keepAlive: true,
});
// Reconnect later using the same connect URL
console.log("Connect URL:", session.connectUrl);
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
keep_alive=True
)
# Reconnect later using the same connect URL
print("Connect URL:", session.connect_url)
```
### Releasing a keep alive session
Keep alive sessions must be explicitly released to stop; otherwise, they'll continue until the session's timeout.
Release keep alive sessions when you're done to avoid being charged for unused browser minutes.
```typescript SDK theme={null}
await bb.sessions.update(session.id, {
status: "REQUEST_RELEASE",
});
```
```typescript API theme={null}
const headers = {
"X-BB-API-Key": "",
"Content-Type": "application/json",
};
const body = JSON.stringify({
status: "REQUEST_RELEASE",
});
fetch("https://api.browserbase.com/v1/sessions/", {
method: "POST",
headers,
body,
})
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```python SDK theme={null}
bb.sessions.update(
session.id,
status="REQUEST_RELEASE"
)
```
```python API theme={null}
import os
import requests
headers = {"X-BB-API-Key": os.environ["BROWSERBASE_API_KEY"]}
json = {
"status": "REQUEST_RELEASE",
}
response = requests.post(
f"https://api.browserbase.com/v1/sessions/",
headers=headers,
json=json,
)
# Raise an exception if there wasn't a good response from the endpoint
response.raise_for_status()
print(response.json())
```
## Keep alive and timeouts
Keep alive sessions still respect [session timeouts](/platform/browser/long-sessions/timeouts). If you need a session that both survives disconnects and runs for an extended period, configure both options:
```typescript theme={null}
const session = await bb.sessions.create({
keepAlive: true,
timeout: 3600, // 1 hour
});
```
Learn how to extend session duration beyond the default timeout.
# Long sessions
Source: https://docs.browserbase.com/platform/browser/long-sessions/overview
Keep sessions running longer and survive disconnects with keep alive and custom timeouts
Keep sessions available after disconnects so you can reconnect without starting over.
Extend session duration beyond the default timeout for long-running tasks.
By default, Browserbase sessions automatically terminate in two scenarios:
1. When a session disconnects
2. When the session reaches its timeout
This behavior optimizes resource utilization and session management, but you may need sessions that run longer or survive disconnections.
Browserbase offers session keep alive and custom timeout to address this need.
## Two ways to extend sessions
| Feature | Problem it solves |
| ---------------------------------------------------------------- | -------------------------------------------------------------- |
| [**Session Timeouts**](/platform/browser/long-sessions/timeouts) | "I need my session to run longer than the default" |
| [**Keep Alive**](/platform/browser/long-sessions/keep-alive) | "I need to disconnect and reconnect without ending my session" |
### When to use each
**Timeouts** extend how long a session can run before it automatically terminates. Use this when you have long-running tasks that exceed the default timeout.
**Keep Alive** lets you disconnect and reconnect to the same session. Use this when your workflow involves multiple connections, when you need resilience against network issues, or when you want to reuse the same session for multiple runs.
### Using both together
If you need a session that both survives disconnects and runs for an extended period, configure both options:
```typescript theme={null}
const session = await bb.sessions.create({
keepAlive: true,
timeout: 3600, // 1 hour
});
```
Session keep alive is only available on paid plans.
## Why keep sessions alive?
Custom timeouts and session keep alive support a broad spectrum of use cases. Key benefits include:
* Avoid interrupting long-running tasks and workflows.
* Connect, disconnect, and reconnect to the same session.
* Keep working with a session without worrying about it timing out.
* Reusing existing sessions is more performant than creating new ones.
**Looking for cloud-deployed execution?** If you don't need persistent sessions or reconnection capabilities, consider [Functions](/platform/runtime/overview) for on-demand browser agents. Functions automatically manage session lifecycle and are ideal for webhooks, scheduled tasks, and API endpoints.
## Keep alive sessions
The `keepAlive` feature allows you to keep sessions alive across disconnects, permitting you to continue using it as long as needed.
### Create a keep alive session
Setting `keepAlive` to `true` will keep the session available for later use. You can reconnect to the keep alive session using the same connection URL as the original session.
Here's an example of how to keep a session alive:
```typescript SDK theme={null}
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create({
keepAlive: true,
});
```
```python SDK theme={null}
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create(
keep_alive=True
)
```
Next time you run the script, you can reconnect to the same session after a disconnect, reusing it for multiple runs.
### Stop a keep alive session
To stop the session, use the Browserbase API or SDK:
```typescript SDK theme={null}
import Browserbase from "browserbase";
const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;
const bb = new Browserbase({
apiKey: BROWSERBASE_API_KEY,
});
// Create a session with keep alive set.
// Then, end it by closing it.
(async () => {
const session = await bb.sessions.create({
keepAlive: true,
});
await bb.sessions.update(session.id, {
status: "REQUEST_RELEASE",
});
})();
```
```typescript API theme={null}
const options = {
method: "POST",
headers: {
"X-BB-API-Key": "",
"Content-Type": "application/json",
},
body: '{"keepAlive": true, "sessionId": "", "status": "REQUEST_RELEASE"}',
};
fetch("https://api.browserbase.com/v1/sessions", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```python SDK theme={null}
from browserbase import Browserbase
import os
# Initialize the SDK
BROWSERBASE_API_KEY = os.environ["BROWSERBASE_API_KEY"]
bb = Browserbase(api_key=BROWSERBASE_API_KEY)
# Create a session with keep alive set
session = bb.sessions.create(keep_alive=True)
# Manually complete the session to end it
bb.sessions.update(session.id, status="REQUEST_RELEASE")
```
```python API theme={null}
# Stop a session
import os
from pprint import pprint
import requests
API_KEY = os.environ["BROWSERBASE_API_KEY"]
SESSION_ID = ""
headers = {"x-bb-api-key": API_KEY}
json = {
"status": "REQUEST_RELEASE",
}
response = requests.post(
f"https://api.browserbase.com/v1/sessions/{SESSION_ID}",
json=json,
headers=headers,
)
# Raise an exception if there wasn't a good response from the endpoint.
response.raise_for_status()
# print the response
pprint(response.json())
```
Stop your keep alive sessions explicitly when no longer
needed. They'll time out eventually, but you may be charged for the unneeded
browser minutes.
## Session timeouts
After the script exceeds the default timeout, you'll see a `TimeoutError`: `Timeout _____ms exceeded`
Browserbase has project-wide settings for session timeout. You can change the session timeout to a different value in the toggle.
### Custom session timeout
You can also set a custom timeout for a created session through code.
If you'd like to set a custom timeout that isn't shown in the toggle, set it in the `createSession` function.
To set a custom timeout for your session, specify the `timeout` option in the API request body or
with the SDK.
```typescript SDK theme={null}
import Browserbase from "browserbase";
const BROWSERBASE_API_KEY = process.env.BROWSERBASE_API_KEY!;
const bb = new Browserbase({
apiKey: BROWSERBASE_API_KEY,
});
// Creates a session with a timeout of 3600 seconds
(async () => {
const session = await bb.sessions.create({
timeout: 3600,
});
})();
```
```typescript API theme={null}
const options = {
method: "POST",
headers: {
"X-BB-API-Key": "",
"Content-Type": "application/json",
},
body: '{"timeout": 3600}',
};
fetch("https://api.browserbase.com/v1/sessions", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```python SDK theme={null}
from browserbase import Browserbase
import os
BROWSERBASE_API_KEY = os.environ["BROWSERBASE_API_KEY"]
bb = Browserbase(
api_key=BROWSERBASE_API_KEY,
)
# Creates a session with a timeout of 3600 seconds
session = bb.sessions.create(
api_timeout=3600
)
```
```python API theme={null}
import os
from pprint import pprint
import requests
BROWSERBASE_API_KEY = os.environ["BROWSERBASE_API_KEY"]
headers = {"x-bb-api-key": BROWSERBASE_API_KEY}
json = {
"api_timeout": 3600,
}
response = requests.post(
"https://api.browserbase.com/v1/sessions", json=json, headers=headers
)
# Raise an exception if there wasn't a good response from the endpoint.
response.raise_for_status()
# print the response
pprint(response.json())
```
Here the timeout has been set to 3600 seconds (1 hour), overriding the default. That means
that unless explicitly closed beforehand, the session will continue running for an hour before
terminating. At disconnect, it will end.
Setting a custom timeout won't keep the session alive after disconnecting. To reconnect
to a session after disconnecting, configure it for keep alive.
The maximum duration of a session is 6 hours. Once a session times out, it can
no longer be used.
## Related guides
Extend session duration beyond the default
Survive disconnects and reconnect to sessions
Watch your session in real time and debug issues after the session has ended
Deploy browser agents with automatic session management
# Timeout
Source: https://docs.browserbase.com/platform/browser/long-sessions/timeouts
Extend session duration beyond the default timeout period.
By default, Browserbase sessions have a timeout period after which they automatically terminate, which prevents sessions from running forever unexpectedly.
You can **extend this duration** either project-wide or per-session, allowing you to keep working without worrying about the session timing out.
The maximum session duration is 6 hours. When a session times out, you'll see a `TimeoutError`: `Timeout _____ms exceeded`.
## Project-wide timeout
You can change the default session timeout for all sessions in your project through the dashboard settings.
## Per-session timeout
You can also set a custom timeout through code by specifying the `timeout` option in the API request body or with the SDK.
This is useful when different sessions need different timeouts, or when you need more granular control than the dashboard toggle provides.
The examples below set timeout to 3600 seconds (1 hour), overriding the project default. Unless explicitly closed beforehand, the session continues running for an hour before terminating. At disconnect, it ends.
```typescript SDK theme={null}
import Browserbase from "browserbase";
const bb = new Browserbase({
apiKey: process.env.BROWSERBASE_API_KEY!,
});
// Creates a session with a timeout of 3600 seconds (1 hour)
const session = await bb.sessions.create({
timeout: 3600,
});
```
```typescript API theme={null}
const options = {
method: "POST",
headers: {
"X-BB-API-Key": "",
"Content-Type": "application/json",
},
body: '{"timeout": 3600}',
};
fetch("https://api.browserbase.com/v1/sessions", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```python SDK theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
# Creates a session with a timeout of 3600 seconds (1 hour)
session = bb.sessions.create(
api_timeout=3600
)
```
```python API theme={null}
import os
import requests
headers = {"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"]}
json = {
"api_timeout": 3600,
}
response = requests.post(
"https://api.browserbase.com/v1/sessions",
json=json,
headers=headers,
)
response.raise_for_status()
print(response.json())
```
## Timeouts and keep alive
Setting a custom timeout extends how long a session can run, but the session still terminates when you disconnect.
If you need to **disconnect and reconnect** to the same session, use [Keep Alive](/platform/browser/long-sessions/keep-alive) instead. For sessions that need both extended duration and reconnection support, configure both options:
```typescript theme={null}
const session = await bb.sessions.create({
keepAlive: true,
timeout: 3600, // 1 hour
});
```
Learn how to keep sessions alive across disconnects.
# Observability
Source: https://docs.browserbase.com/platform/browser/observability/observability
Debug and monitor your browser sessions with logs, metrics, and real-time inspection
Watch and interact with browser sessions in real-time as they run.
Replay every session as a video recording to debug issues after the fact.
Stream session replays as HLS to embed playback in your own application.
Browserbase provides comprehensive observability tools to debug and monitor your browser sessions. Access these tools through the Session Inspector in the Dashboard or retrieve data programmatically via the API.
The Session Inspector is accessible by clicking any session in the [Dashboard](https://www.browserbase.com/sessions).
## Dashboard
### Video recordings
Every session is automatically captured as a video recording, supporting up to 10 tabs. Video recordings are available in the Session Inspector for playback and debugging.
To embed playback in your own application, stream session replays as HLS through the Session Replay API.
### Live view
Debug running sessions in real-time using the Live Debug URL.
The `Copy Debug URL` button appears in the Session Inspector when a session is actively running.
For more on Live View, see [Session Live View](/platform/browser/observability/session-live-view).
### Status bar
The Status Bar displays session metadata and status information.
| Property | Description |
| --------------------------------------------- | ----------------------------------------------------------------------------- |
| Session ID | Unique identifier for the session |
| Status | Current status and termination reason if applicable |
| Started | Session start timestamp |
| [Region](/optimizations/latency/multi-region) | Region where the session ran |
| Duration | Total session length |
| [Proxy Bandwidth](/platform/identity/proxies) | Data transferred through proxy (MB). Only displayed when proxies are enabled. |
| Settings | Session configuration (e.g., `keepAlive`, `context`) |
| User Metadata | Custom metadata attached to the session |
| Extension ID | Identifier for any browser extension used |
| Expires | When the session data expires and is deleted |
### Events and pages
The Events view shows a timeline of activity during the session.
This view includes:
* Pages loaded during the session
* CDP events (`Runtime.*`, `Page.*`, `Input.*`, `Log.*`)
* Network requests and responses
### Stagehand
The Stagehand tab provides inspection tools for sessions created with [Stagehand](https://www.stagehand.dev/).
Click any row to expand details:
Available information:
* Token usage
* Execution time
* Extraction schemas
* Execution results
For `extract` calls, switch between JSON and [Zod](https://zod.dev/) schema formats:
### Console logs
View browser console logs emitted by the [Web Console API](https://developer.mozilla.org/en-US/docs/Web/API/console) (`console.log()`, `console.error()`, etc.). These are logs generated by JavaScript running on the page.
Common console messages:
* `browser-solving-started` / `browser-solving-completed` - Captcha solving events
* `Starting recording` - Recording initialization
### Network logs
View all HTTP network requests and responses captured via Chrome DevTools Protocol ([`Network`](https://chromedevtools.github.io/devtools-protocol/tot/Network/) events).
Use network logs to:
* Debug failed requests
* Analyze proxy bandwidth usage
* Identify slow or blocked resources
## Session logs API
Retrieve session logs programmatically for automated processing or custom tooling. The Session Logs API returns CDP (Chrome DevTools Protocol) events including console logs, network activity, and page lifecycle events.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const logs = await bb.sessions.logs.list(sessionId);
console.log(logs);
```
```python theme={null}
from browserbase import Browserbase
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
logs = bb.sessions.logs.list(session_id)
print(logs)
```
```bash theme={null}
curl https://api.browserbase.com/v1/sessions//logs \
-H "X-BB-API-Key: $BROWSERBASE_API_KEY"
```
See [Sessions API](/reference/api/session-logs) for more details.
## HAR recording
HAR (HTTP Archive) files capture detailed network activity for offline analysis. Use Playwright's tracing feature to record HAR data locally.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
import { chromium } from "playwright-core";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0];
// Start HAR recording
const harFile = `recording-${Date.now()}.har`;
await context.routeFromHAR(harFile, {
url: "**/*",
update: true,
updateContent: "embed",
updateMode: "full"
});
// Start tracing for local network capture
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true
});
await page.goto("https://news.ycombinator.com");
// Stop tracing and save
const traceFile = `trace-${Date.now()}.zip`;
await context.tracing.stop({ path: traceFile });
await page.close();
await browser.close();
console.log(`HAR: ${harFile}, Trace: ${traceFile}`);
```
```python theme={null}
from browserbase import Browserbase
from playwright.sync_api import sync_playwright
import os
import time
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
# Start HAR recording
har_file = f"recording-{int(time.time())}.har"
context.route_from_har(har_file, url="**/*", update=True,
update_content="embed", update_mode="full")
# Start tracing for local network capture
context.tracing.start(screenshots=True, snapshots=True, sources=True)
page.goto("https://news.ycombinator.com")
# Stop tracing and save
trace_file = f"trace-{int(time.time())}.zip"
context.tracing.stop(path=trace_file)
page.close()
browser.close()
print(f"HAR: {har_file}, Trace: {trace_file}")
```
View trace files with: `npx playwright show-trace trace-file.zip`
HAR files created with `routeFromHAR` are stored on the remote instance. Use tracing to capture network data locally.
## Capturing browser console logs
You can capture browser console logs programmatically during your session using Playwright's console event listener. This gives you real-time access to `console.log()`, `console.error()`, and other Web Console API calls as they happen.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
import { chromium } from "playwright-core";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0];
// Capture console logs
page.on("console", (msg) => {
console.log(`[${msg.type()}] ${msg.text()}`);
});
await page.goto("https://news.ycombinator.com");
await page.close();
await browser.close();
```
```python theme={null}
from browserbase import Browserbase
from playwright.sync_api import sync_playwright
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
# Capture console logs
page.on("console", lambda msg: print(f"[{msg.type}] {msg.text}"))
page.goto("https://news.ycombinator.com")
page.close()
browser.close()
```
## Debugging tips
| Issue | Solution |
| ------------------------------- | ------------------------------------------------------------------------------------------- |
| Session terminated unexpectedly | Check Status Bar for termination reason |
| Selector not found | Use DOM view to inspect element state at failure point |
| Network request failed | Check Network tab for status codes and response details |
| Bot protection issues | Review console logs for solving events, check [Agent Identity](/platform/identity/overview) |
Questions? Email [support@browserbase.com](mailto:support@browserbase.com)
# Recording downloads
Source: https://docs.browserbase.com/platform/browser/observability/recording-downloads
Assemble and download session recordings as MP4 files, one per page.
The Recording Downloads API turns a session's recording into downloadable MP4 files, one per recorded page (tab). Assembly runs asynchronously: request the downloads, then poll for status and a short-lived download URL. To stream playback in-app instead of downloading files, use the [Session Replay API](/platform/browser/observability/session-replay).
Download a completed session's recording as MP4 files, one per tab. Browserbase assembles them on demand after the session ends.
## Quickstart
Given a `sessionId` from a session that has ended, request the downloads, then poll until each page is `COMPLETED`.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const sessionId = "";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
// Request one MP4 per recorded page (tab); each page starts PENDING.
await bb.sessions.recording.downloads.create(sessionId);
// Poll until every page has finished assembling.
let downloads;
do {
await new Promise((resolve) => setTimeout(resolve, 3000));
({ downloads } = await bb.sessions.recording.downloads.list(sessionId));
} while (downloads.some((page) => page.status === "PENDING"));
for (const page of downloads) {
console.log(page.pageId, page.status, page.downloadUrl);
}
```
```python theme={null}
import os
import time
from browserbase import Browserbase
session_id = ""
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
# Request one MP4 per recorded page (tab); each page starts PENDING.
bb.sessions.recording.downloads.create(session_id)
# Poll until every page has finished assembling.
while True:
downloads = bb.sessions.recording.downloads.list(session_id).downloads
if not any(page.status == "PENDING" for page in downloads):
break
time.sleep(3)
for page in downloads:
print(page.page_id, page.status, page.download_url)
```
```bash theme={null}
# Request the downloads (each page starts PENDING).
curl -X POST https://api.browserbase.com/v1/sessions/$SESSION_ID/recording/downloads \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
# Poll for status and download URLs.
curl https://api.browserbase.com/v1/sessions/$SESSION_ID/recording/downloads \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
```
## How it works
Assembly of the recording is asynchronous, driven by two calls on the same path:
`POST /v1/sessions/{id}/recording/downloads` enqueues one MP4 per recorded page (tab). Assuming the session has ended and is still within its retention window, it returns `202` and marks every page `PENDING`. Re-posting re-enqueues all pages and retries any that failed, so it is safe to call again.
`GET /v1/sessions/{id}/recording/downloads` returns the current state of each page. Poll it until the pages you need reach `COMPLETED` (or `FAILED`).
Each `COMPLETED` page carries a short-lived `downloadUrl`. Opening it downloads the MP4 directly and saves it as `{sessionId}-{pageId}.mp4`.
### Status values
Each page reports one of four states:
| Status | Meaning |
| --------------- | ---------------------------------------------------------------- |
| `NOT_REQUESTED` | You haven't requested a download for the session yet. |
| `PENDING` | Browserbase is assembling the MP4 (queued or in progress). |
| `COMPLETED` | The MP4 is ready; `downloadUrl` is present on non-BYOS projects. |
| `FAILED` | Assembly failed. POST again to retry the page. |
## Response shape
Each entry corresponds to one recorded page (tab).
```json theme={null}
{
"downloads": [
{
"pageId": "0",
"status": "COMPLETED",
"downloadUrl": "https://.../recording-renditions/.../0.mp4?token=...",
"completedAt": "2026-06-30T18:24:05.000Z"
},
{
"pageId": "1",
"status": "PENDING"
}
]
}
```
Field semantics:
* `pageId` identifies the recorded page within the session, ascending from `"0"`.
* `downloadUrl` and `completedAt` are present only when `status` is `COMPLETED` on a standard (non-BYOS) project.
## Multitab
Browserbase records up to 10 tabs open concurrently per session, and produces one MP4 per recorded page. Request downloads once; poll the list to collect each page's file as it finishes.
## Download URL expiration
Browserbase signs each `downloadUrl`, and it expires **six hours** after the API issues it. The GET endpoint re-mints the URL on every call, so if a link expires, list the downloads again to get a fresh one.
## Availability window
You can assemble recording downloads while Browserbase still retains the session's recording source, for up to **31 days** after the session ends on non-BYOS projects. After that, the source is gone and the API returns `410 Gone`.
Downloads become available only after a session **ends**. Requesting them for a running session returns `409 Conflict`.
## Bring your own storage (BYOS)
On projects using [Bring Your Own Storage](/account/enterprise/byos-setup-guide), Browserbase writes each assembled MP4 to your own S3 bucket instead of Browserbase storage. The API still reports per-page `status` but omits `downloadUrl` and `completedAt`, so fetch the files from your bucket. BYOS projects retain the recording source for **24 hours**.
## Rate limits
The POST endpoint is limited to **5 requests per minute** per project, since each call kicks off assembly. [BYOS](/account/enterprise/byos-setup-guide) enables higher rate limits for enterprise customers. See [Concurrency & Rate Limits](/optimizations/concurrency/overview) for the retry pattern.
## Recording downloads vs. Session replay
* **Recording downloads** produce standalone **MP4 files** you fetch and store (best for archiving and offline review).
* **[Session replay](/platform/browser/observability/session-replay)** streams the recording as **HLS** for embedded, in-app playback.
Both draw from the same underlying session recording.
## Disabling recordings
Sessions you create with `recordSession: false` produce no recording, so there is nothing to download. Both endpoints return `422 Unprocessable Content` with `{"message": "Recording was disabled for this session"}`. See [Session Recording](/platform/browser/observability/session-recording#disabling-video-recordings).
## Troubleshooting
**GET returns `409 Conflict`:**
* The session has not ended yet. Browserbase assembles downloads only after a session completes.
**GET returns `410 Gone`:**
* The recording aged out of its retention window (up to 31 days on non-BYOS projects, 24 hours when using BYOS). Browserbase can no longer assemble it.
**A page stays `PENDING`:**
* Large recordings take longer to assemble. Keep polling; re-POST only if a page reports `FAILED`.
**`downloadUrl` is missing on a `COMPLETED` page:**
* The project uses BYOS. The MP4 is in your own bucket; the API does not return a signed URL for BYOS renditions.
## Related
Stream a session's recording as HLS for embedded, in-app playback.
Replay every session as a video recording to debug after the fact.
Route recording renditions to your own S3 bucket.
API reference for requesting and listing per-page MP4 downloads.
# Session live view
Source: https://docs.browserbase.com/platform/browser/observability/session-live-view
An interactive window to display or control a browser session.
On any running browser session - watch, click, type, and scroll in real-time.
***
## Uses
While Browserbase helps with [bot protection systems](/platform/identity/overview),
data extraction, and [reliable file downloads](/platform/browser/files/downloads), some scenarios
remain challenging to fully automate for technical or data-privacy reasons.
Live Views can be useful for:
* Debugging and observability - watch everything happening live, or share with users or coworkers
* Human in the loop - instantly take control or provide input
* handle iframes - loaded content might be external or change without notice, causing errors without human intervention
* delegate credentials - give control to the end user
* upload files - see the [uploads guide](/platform/browser/files/uploads#manual-upload-through-live-view) to enable uploads through the live view
* Embedding - use within an application (both desktop and mobile)
***
## Getting started
Need help getting started? Check out the [Create a Browser Session](/platform/browser/getting-started/create-browser-session) and [Using Browser Sessions](/platform/browser/getting-started/using-browser-session) guides.
Also check out the [Live Views API endpoint](/reference/api/session-live-urls).
```typescript Node.js theme={null}
const liveViewLinks = await bb.sessions.debug(session.id);
const liveViewLink = liveViewLinks.debuggerFullscreenUrl;
console.log(`Live View Link: ${liveViewLink}`);
// [Optional] If you want to automatically open up the live view URL in your browser tab, you can also run the lines below:
import open from 'open';
open(liveViewLink);
```
```python Python theme={null}
live_view_links = bb.sessions.debug(session.id)
live_view_link = live_view_links.debuggerFullscreenUrl
print(f"Live View Link: {live_view_link}")
# [Optional] If you want to automatically open up the live view URL in your browser tab, you can also run the lines below:
import webbrowser
webbrowser.open(live_view_link)
```
***
## Multitab
Each tab has a unique live view url.
The `pages` property contains all live view urls.
Listen for the [Playwright new tab event](https://playwright.dev/docs/pages#handling-new-pages) (or equivalent in other libraries) to fetch new live view urls as tabs open.
```typescript Node.js theme={null}
// Open a new tab and navigate to google
const newTab = await defaultContext.newPage();
newTab.goto("https://www.google.com");
// Get the live view links after the new tab is opened - then access the second tab
const liveViewLinks = await bb.sessions.debug(session.id);
const allTabs = liveViewLinks.pages;
const secondTabLiveViewLink = allTabs[1].debuggerFullscreenUrl;
console.log(`Second Tab Live View Link: ${secondTabLiveViewLink}`);
```
```python Python theme={null}
# Open a new tab and navigate to google
new_tab = await default_context.new_page()
new_tab.goto("https://www.google.com")
# Get the live view links after the new tab is opened - then access the second tab
live_view_links = bb.sessions.debug(session.id)
all_tabs = live_view_links.pages
second_tab_live_view_link = all_tabs[1].debuggerFullscreenUrl
print(f"Second Tab Live View Link: {second_tab_live_view_link}")
```
***
## Embedding
Add the live view link to an iframe in your frontend to embed it.
```html Read-only theme={null}
```
```html Read/Write theme={null}
```
### Mobile
Show a mobile live view by setting a session's [viewport](/reference/api/create-a-session#body-browser-settings-viewport).
```typescript Node.js theme={null}
// Standard android mobile dimensions
browserSettings: {
viewport: {
width: 360,
height: 800,
},
},
// ...other session configuration options
```
```python Python theme={null}
# Standard android mobile dimensions
browser_settings={
"viewport": {
"width": 360,
"height": 800
}
},
# ...other session configuration options
```
To display a keyboard with a mobile live view, use a library like [react-simple-keyboard](https://www.npmjs.com/package/react-simple-keyboard).
Mobile keyboards aren't officially supported. Desktop works natively, but for mobile you'll need to handle key events and send them to your automation script (like via HTTP or WebSocket). Once there, call `page.keyboard.press()` to forward them into the session.
Some virtual keyboard keys need to be mapped to your framework's key names (e.g. `{ent}` → `"Enter"`). You may need to override keys like Tab so they're sent to the session and not processed by the local browser.
### Handling disconnects
When the browser session ends, the live view will show a disconnect message:
You can listen for this event programmatically:
```javascript theme={null}
window.addEventListener("message", function (event) {
if (event.data === "browserbase-disconnected") {
// Handle the disconnection (e.g., show a message, clean up resources)
console.log("Live view disconnected");
}
});
```
***
## Styling
### Browser with borders
Mimic a real browser with borders.
```typescript Node.js theme={null}
const liveViewLinks = await bb.sessions.debug(session.id);
const liveViewLink = liveViewLinks.debuggerUrl;
console.log(`Live View Link - with borders: ${liveViewLink}`);
```
```python Python theme={null}
live_view_links = bb.sessions.debug(session.id)
live_view_link = live_view_links.debuggerUrl
print(f"Live View Link - with borders: {live_view_link}")
```
### Hide the navbar
The live view includes a navbar at the top for context and navigation.
Hide it to maximize the visible area or when your UI already provides context.
```typescript Node.js theme={null}
const hiddenNavbarUrl = `${liveViewLink}&navbar=false`;
```
```python Python theme={null}
hidden_navbar_url = f"{live_view_link}&navbar=false"
```
### Hide the scrollbar
```typescript Playwright/Puppeteer theme={null}
// Navigate to the page
await page.goto("https://news.ycombinator.com/");
// Hide the scrollbar
await page.evaluate(() => {
const style = document.createElement("style");
style.textContent = `::-webkit-scrollbar { display: none; }`;
document.head.appendChild(style);
});
```
```typescript Selenium theme={null}
// Navigate to the page
await driver.get("https://news.ycombinator.com/");
// Hide the scrollbar
await driver.executeScript(`
const style = document.createElement("style");
style.textContent = "::-webkit-scrollbar { display: none; }";
document.head.appendChild(style);
`);
```
```python Playwright theme={null}
# Navigate to the page
page.goto("https://www.browserbase.com")
# Hide the scrollbar
page.evaluate("""
const style = document.createElement('style');
style.textContent = '::-webkit-scrollbar { display: none; }';
document.head.appendChild(style);
""")
```
```python Selenium theme={null}
# Navigate to the page
driver.get("https://www.browserbase.org")
# Hide the scrollbar
driver.execute_script("""
style = document.createElement("style");
style.textContent = "::-webkit-scrollbar { display: none; }";
document.head.appendChild(style);
""")
```
***
## Troubleshooting
1. **Blank or empty window**
You may be on another tab. Check if there are multiple tabs open via [the web session inspector](/platform/browser/observability/observability) or [the pages list](#multitab).
2. **Lag**
Check out the [performance guide](/optimizations/latency/speed-optimization).
3. **Looks off**
Often caused by headless browser rendering differences. Some issues may be fixable by adjusting CSS styling through `page.evaluate()`.
4. **Lost Connection**
If the live view loses its connection to the browser, the iframe will post a `browserbase-disconnected` message. See [Handling Disconnects](#handling-disconnects) for how to listen for this event.
# Session recording (rrweb)
Source: https://docs.browserbase.com/platform/browser/observability/session-recording
Every session is automatically recorded so you can inspect actions and debug issues
**rrweb is being deprecated.** The rrweb-based DOM replay API and related tooling are being deprecated. Video recordings in the Dashboard remain fully supported. If you need to continue using rrweb, please contact [support@browserbase.com](mailto:support@browserbase.com).
Video recordings in the Dashboard capture up to 10 tabs as separate video streams. To embed playback in your own application, stream session replays as HLS through the [Session Replay API](/platform/browser/observability/session-replay).
Browserbase automatically records every session as a video. You can replay these video recordings to inspect the actions performed, review network requests, and debug issues page by page.
Video recordings are available in the [Dashboard](https://www.browserbase.com/sessions). To stream session replays into your own application via HLS, use the [Session Replay API](/platform/browser/observability/session-replay).
## Quickstart
### Install the SDK
```bash npm theme={null}
npm install @browserbasehq/sdk playwright-core
```
```bash pnpm theme={null}
pnpm install @browserbasehq/sdk playwright-core
```
```bash yarn theme={null}
yarn add @browserbasehq/sdk playwright-core
```
```bash theme={null}
pip install browserbase playwright
```
### Run a session
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
import { chromium } from "playwright-core";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const session = await bb.sessions.create();
const browser = await chromium.connectOverCDP(session.connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0];
await page.goto("https://news.ycombinator.com");
await page.waitForTimeout(3000);
await page.close();
await browser.close();
console.log(`View recording: https://browserbase.com/sessions/${session.id}`);
```
```python theme={null}
from browserbase import Browserbase
from playwright.sync_api import sync_playwright
import os
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
session = bb.sessions.create()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
page.goto("https://news.ycombinator.com")
page.wait_for_timeout(3000)
page.close()
browser.close()
print(f"View recording: https://browserbase.com/sessions/{session.id}")
```
After the session completes, the recording is available at `https://browserbase.com/sessions/{session.id}`.
## Viewing video recordings
The video recording player supports:
* Playback speed control (0.5x, 1x, 2x, 4x)
* Timeline navigation
* Pause/resume at any point
## Multitab support
Video recordings capture up to 10 tabs as separate video streams. Each tab can be viewed individually in the Session Inspector.
The rrweb DOM replay API (`recording.retrieve()`) only captures the primary tab. For multitab sessions, use the video recordings in the Dashboard.
## Retrieving rrweb DOM replays
**Deprecated.** The rrweb DOM replay API is being deprecated. If you need to continue using rrweb, please contact [support@browserbase.com](mailto:support@browserbase.com).
For programmatic access to the same video that plays in the Dashboard, see [Session Replay](/platform/browser/observability/session-replay); it returns an HLS stream you can embed using [hls.js](https://github.com/video-dev/hls.js) or any HLS-capable player.
### Via SDK
Retrieve rrweb DOM replay events programmatically for custom playback:
```typescript theme={null}
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const recording = await bb.sessions.recording.retrieve(sessionId);
console.log(recording);
```
```python theme={null}
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
recording = bb.sessions.recording.retrieve(session_id)
print(recording)
```
The `recording.retrieve()` method returns rrweb DOM replay events. This is separate from video recordings, which are only available in the Dashboard.
### Embedding the rrweb player
**Deprecated.** The rrweb player integration is being deprecated. If you need to continue using rrweb, please contact [support@browserbase.com](mailto:support@browserbase.com).
To embed rrweb DOM replays in your application, use the rrweb player:
```typescript theme={null}
import rrwebPlayer from "rrweb-player";
import "rrweb-player/dist/style.css";
const player = new rrwebPlayer({
target: document.body,
props: {
events: recording.events,
width: 1024,
height: 576,
skipInactive: true,
showController: true,
autoPlay: false,
},
});
// Control playback
player.addEventListener("play", () => console.log("Started"));
player.addEventListener("pause", () => console.log("Paused"));
player.addEventListener("finish", () => console.log("Finished"));
// Clean up when done
player.destroy();
```
## Disabling video recordings
To disable video recording for a session, set `recordSession` to `false` in your browser settings:
```typescript theme={null}
const session = await bb.sessions.create({
browserSettings: {
recordSession: false,
},
});
```
```python theme={null}
session = bb.sessions.create(
browser_settings={"recordSession": False},
)
```
Disabling recording also disables rrweb DOM replay data. [Live View](/platform/browser/observability/session-live-view) remains available for real-time debugging.
## Troubleshooting
**rrweb DOM replay doesn't match session:**
* rrweb DOM replays are DOM reconstructions and may not always be fully accurate
* Video recordings in the Dashboard provide the most authentic representation
* Recording starts when the first page loads, not when the browser launches
* Use [Live View](/platform/browser/observability/session-live-view) for real-time debugging
**rrweb DOM replay not available:**
* Some sites may block rrweb DOM replays (e.g., Opentable, Salesforce family). Video recordings in the Dashboard should be unaffected.
Questions? Email [support@browserbase.com](mailto:support@browserbase.com)
# Session replay
Source: https://docs.browserbase.com/platform/browser/observability/session-replay
Stream session replays as HLS to embed playback in your own application.
The Session Replay API streams session replays as HLS for embedded playback. Each tab's recording gets its own playlist; play them back with [hls.js](https://github.com/video-dev/hls.js) or any HLS-capable player. The same recordings power the Dashboard's [Session Inspector](/platform/browser/observability/observability).
Embed session playback in your own application, dashboard, ticketing tool, or QA workflow. Browserbase records every session by default.
## Quickstart
Given a `sessionId` from [Create a Session](/platform/browser/getting-started/create-browser-session), fetch a replay in two calls: list the pages, then fetch the playlist for any page.
```typescript theme={null}
import { Browserbase } from "@browserbasehq/sdk";
const sessionId = "";
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
const meta = await bb.sessions.replays.retrieve(sessionId);
const firstPage = meta.pages[0];
const playlist = await bb.sessions.replays.retrievePage(
sessionId,
firstPage.pageId,
);
const m3u8 = await playlist.text();
// The body is an HLS playlist. Each "https://..." line is a
// signed CDN segment URL valid for 6 hours; hand the whole body
// to any HLS player.
const firstSegmentUrl = m3u8
.split("\n")
.find((line) => line.startsWith("https://"));
console.log(firstSegmentUrl);
```
```python theme={null}
import os
from browserbase import Browserbase
session_id = ""
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
meta = bb.sessions.replays.retrieve(session_id)
first_page = meta.pages[0]
playlist = bb.sessions.replays.retrieve_page(
first_page.page_id, id=session_id,
)
m3u8 = playlist.read().decode("utf-8")
# The body is an HLS playlist. Each "https://..." line is a
# signed CDN segment URL valid for 6 hours; hand the whole body
# to any HLS player.
first_segment_url = next(
line for line in m3u8.splitlines() if line.startswith("https://")
)
print(first_segment_url)
```
```bash theme={null}
curl https://api.browserbase.com/v1/sessions/$SESSION_ID/replays \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
curl https://api.browserbase.com/v1/sessions/$SESSION_ID/replays/0 \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
```
The metadata response lists each tab's recording. The playlist response is an HLS `.m3u8` document: a plain-text manifest that points at a sequence of fragmented-MP4 (`.m4s`) segments served from the Browserbase CDN. Any HLS player fetches the manifest and streams the segments in order. For background on the format, see the [HTTP Live Streaming overview on Wikipedia](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) or the spec in [RFC 8216](https://datatracker.ietf.org/doc/html/rfc8216).
## Embedding a player
Any HLS-capable player works. Pick whichever fits your stack; the playlist URL is the same in all cases.
In each snippet below, the `src` / `loadSource` URL points at a route on **your own backend**, not at `api.browserbase.com` directly. Calling the Session Replay API from the browser would expose your `BROWSERBASE_API_KEY` to every viewer. See [Recommended integration pattern](#recommended-integration-pattern) below for the proxy shape.
[hls.js](https://github.com/video-dev/hls.js) is the most common choice for cross-browser HLS playback, and the recommended path for Firefox desktop, which has no native HLS support.
```html theme={null}
```
[Shaka Player](https://github.com/shaka-project/shaka-player) is Google's open-source player and supports both HLS and DASH.
```html theme={null}
```
[Video.js](https://videojs.com/) ships HLS support via `@videojs/http-streaming` and exposes a styled UI out of the box.
```html theme={null}
```
Safari and Chromium-based browsers (Chrome, Edge) play HLS natively, with no JavaScript player needed. Firefox desktop has no native HLS support; use hls.js (or another MSE-based player) there.
On Chromium, native HLS can fall through your library's `canPlayType` check, so segments end up on the browser's CORS path instead of the library's. If you mix native and library playback, prefer `Hls.isSupported()` (or your library's equivalent) to keep the fetch path consistent.
```html theme={null}
```
Browsers only autoplay when the video is muted; the `muted` attribute is required even for short replays. Drop `autoplay` and `muted` if you want a click-to-play UX.
The playlist body references signed segment URLs on the Browserbase CDN. The browser fetches those segments directly, so your backend does not need to proxy them.
## Multitab
Each recorded tab appears as its own page in the metadata response, with its own playlist URL.
```json theme={null}
{
"pages": [
{
"pageId": "0",
"url": "/v1/sessions/0a4c2f10-d7c0-4af8-9efb-a8d5c9f1b2e6/replays/0",
"startTimeMs": 0,
"endTimeMs": 121382
},
{
"pageId": "1",
"url": "/v1/sessions/0a4c2f10-d7c0-4af8-9efb-a8d5c9f1b2e6/replays/1",
"startTimeMs": 13001,
"endTimeMs": 121382
}
],
"pageCount": 2
}
```
Field semantics:
* `url` is a relative path against `https://api.browserbase.com`.
* `startTimeMs` and `endTimeMs` are **milliseconds from session start**, not Unix epoch.
* The API returns pages ordered by `pageId` ascending.
Browserbase records up to 10 tabs open concurrently per session. Tabs you open while 10 are already recording will not appear in the replay.
## Recommended integration pattern
Your backend calls `GET /v1/sessions/{id}/replays/{pageId}` with `x-bb-api-key` and forwards the `.m3u8` body to your frontend unchanged.
```typescript theme={null}
import express from "express";
import { Browserbase } from "@browserbasehq/sdk";
const app = express();
const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
app.get("/replays/:sessionId/:pageId", async (req, res) => {
const { sessionId, pageId } = req.params;
try {
const playlist = await bb.sessions.replays.retrievePage(
sessionId,
pageId,
);
res
.type("application/vnd.apple.mpegurl")
.send(await playlist.text());
} catch (err: any) {
res.status(err?.status ?? 500).send(err?.message ?? "Replay error");
}
});
```
```python theme={null}
import os
from browserbase import Browserbase
from fastapi import FastAPI, Response
app = FastAPI()
bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
@app.get("/replays/{session_id}/{page_id}")
def get_replay(session_id: str, page_id: str):
playlist = bb.sessions.replays.retrieve_page(page_id, id=session_id)
return Response(
content=playlist.read(),
media_type="application/vnd.apple.mpegurl",
)
```
Your frontend then points its HLS player at `/replays//` on your own origin.
Point your HLS player at the route on your backend that returns the playlist. The player parses the manifest and starts requesting segments.
Segment URLs in the playlist are pre-signed CDN links. The browser fetches them directly; no proxying through your servers.
This avoids double-egress through your servers while keeping the API key off the browser.
**Alternative: skip the proxy.** If your backend renders the playback page server-side, you can fetch the playlist there, embed the `.m3u8` body in the page (or hand it to the player as a `Blob` URL), and skip the proxy route entirely. Segment URLs in the body are signed and work directly from the browser regardless of how the body got there.
## Segment URL expiration
Browserbase signs each playlist's segment URLs, and they expire six hours after the API issues the playlist. Most playback sessions finish well before that. To resume playback beyond the expiry window, re-request the playlist; the API mints fresh segment URLs on every call.
## Rate limits
Browserbase rate limits the playlist endpoint (`GET /v1/sessions/{id}/replays/{pageId}`) to **120 requests per minute** per project (sustained 2 RPS). Bursts above 2 RPS still succeed as long as your project stays under 120 requests per minute. Going over returns a `429` status code with standard rate-limit response headers; see [Concurrency & Rate Limits](/optimizations/concurrency/overview) for the retry pattern.
## Disabling recordings
The Session Replay API serves the same recordings the Dashboard plays. To skip recording for a session entirely, set `recordSession` to `false` when you create it; see [Session Recording](/platform/browser/observability/session-recording#disabling-video-recordings).
Sessions you create with `recordSession: false` produce no replay. Both endpoints return `404 Not Found` with `{"message": "Replay not found"}`. [Live View](/platform/browser/observability/session-live-view) remains available for real-time debugging.
## Troubleshooting
**Player loads the playlist but no video plays:**
* Some HLS players don't auto-play after `loadedmetadata`. Call `video.play()` manually.
* Most browsers block autoplay unless the video is muted; set `muted` on the `