Browser agents
If your agent drives a browser, sign in as usual, then call session.attach(page). From then on, every request the page makes to the site (page loads, scripts, its own fetch calls) carries the agent token and a fresh signature. Requests to any other origin go out untouched, and your private key never enters the browser: the signing runs in your process and the browser only sees finished headers.
It works with a Playwright Page or BrowserContext (a context also covers popups and new tabs) and a Puppeteer Page, on a local Chromium or a cloud browser you reach over CDP.
// npm install @trybage/sdk @onkernel/sdk playwrightimport Kernel from "@onkernel/sdk";import { chromium } from "playwright";import { BageAgent } from "@trybage/sdk/agent";
const { issuer, client_id, private_jwk } = JSON.parse(process.env.BAGE_AGENT_KEY ?? "{}");const agent = new BageAgent({ issuer, clientId: client_id, privateJwk: private_jwk });const session = await agent.signIn({ site: process.env.SITE ?? "https://shop.example", loginHint: process.env.PERSON ?? "alex@example.com", scopes: ["browse", "cart"],});
// A Kernel cloud browser (KERNEL_API_KEY), driven over CDP.const kernel = new Kernel();const remote = await kernel.browsers.create({ stealth: true, timeout_seconds: 300 });try { const browser = await chromium.connectOverCDP(remote.cdp_ws_url); const page = browser.contexts()[0]?.pages()[0] ?? (await browser.newPage());
// Every request to the site now carries the token and a fresh signature. Other origins get nothing. await session.attach(page); await page.goto(new URL(process.env.PAGE ?? "/products", session.site).href); console.log(await page.textContent("body")); await browser.close();} finally { await kernel.browsers.deleteByID(remote.session_id);}// npm install @trybage/sdk @browserbasehq/sdk playwrightimport Browserbase from "@browserbasehq/sdk";import { chromium } from "playwright";import { BageAgent } from "@trybage/sdk/agent";
const { issuer, client_id, private_jwk } = JSON.parse(process.env.BAGE_AGENT_KEY ?? "{}");const agent = new BageAgent({ issuer, clientId: client_id, privateJwk: private_jwk });const session = await agent.signIn({ site: process.env.SITE ?? "https://shop.example", loginHint: process.env.PERSON ?? "alex@example.com", scopes: ["browse", "cart"],});
// A Browserbase cloud browser (BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID), driven over CDP.const browserbase = new Browserbase();const remote = await browserbase.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID! });const browser = await chromium.connectOverCDP(remote.connectUrl);const page = browser.contexts()[0]?.pages()[0] ?? (await browser.newPage());
// Every request to the site now carries the token and a fresh signature. Other origins get nothing.await session.attach(page);await page.goto(new URL(process.env.PAGE ?? "/products", session.site).href);console.log(await page.textContent("body"));await browser.close(); // ends the Browserbase session# pip install "trybage[playwright]"import jsonimport os
from playwright.sync_api import sync_playwrightfrom trybage import BageAgent
agent = BageAgent.from_key_file(os.environ["BAGE_AGENT_KEY"]) # the key file from the consolesession = agent.sign_in(os.environ.get("SITE", "https://shop.example"), os.environ.get("PERSON", "alex@example.com"), ["browse", "cart"])
with sync_playwright() as p: # Or a Kernel or Browserbase browser: p.chromium.connect_over_cdp(remote.cdp_ws_url) browser = p.chromium.launch(executable_path=os.environ.get("CHROMIUM_PATH") or None) page = browser.new_page() session.attach(page) # every request to the site is signed; other origins get nothing page.goto(session.site + "/products") print(page.text_content("body")) browser.close()attach returns a function that stops signing (await it in TypeScript and async Python). With async Playwright in Python, use await agent.sign_in_async(...) and detach = await session.attach(page).
Things to know
Section titled “Things to know”- The site must accept Bage on its pages, not only on its API, since page loads are signed too.
- Tokens last 15 minutes. For a longer task, call
signInagain andattachthe new session. With an Always grant this is silent. - Each signed request makes a round trip through your process over CDP, so a remote browser is a little slower on the site’s requests. Other origins aren’t intercepted.
- More hosts: if the site serves pages or its API from another host it accepts the token on, pass
{ extraOrigins: ["https://api.shop.example"] }. - Your own HTTP client:
session.signedHeaders(method, url)(Python:session.signed_headers) returns the three headers for one request.