Browsa
  1. Guides
  2. OpenAI Agents SDK

Give an OpenAI agent a browser.

Give an agent built with the OpenAI Agents SDK a real Chrome on Browsa. Playwright connects over CDP, and four small function tools let the model open pages, read them, click and fill in forms.

  • TimeAbout 10 minutes
  • Cost1 credit per 5-minute browser, plus your model costs
  • Last tested
What we tested. On November 7, 2026 we ran this script's connection code against a live Browsa browser with openai-agents 0.24.0 and Playwright 1.64.1, then called the script's own open_url, read_page and click tools directly: they opened https://example.com, read it and followed its link. We didn't run Runner.run(), which needs your OpenAI key; that part follows the Agents SDK's documented API.

Prerequisites

  • A Browsa API key (it starts with agt_). Sign up for 100 free credits, no card.
  • Python 3.10 or newer.
  • An OpenAI API key.

Install

Terminal
pip install openai-agents playwright browsa

Then put your keys in the environment:

Terminal
export BROWSA_API_KEY="agt_live_..."
export OPENAI_API_KEY="sk-..."

You don't need playwright install: the browser runs on Browsa, and Playwright only connects to it.

The script

One file. The tools take the Playwright page from the run context, so every tool call works on the same tab.

agent.py
import asyncio
import os
from dataclasses import dataclass

from agents import Agent, RunContextWrapper, Runner, function_tool
from browsa import Client
from playwright.async_api import Page, async_playwright

BROWSA_API_KEY = os.environ["BROWSA_API_KEY"]
TASK = "Open https://example.com, follow its link, and tell me the title of the page you land on."


@dataclass
class Browser:
    page: Page


@function_tool
async def open_url(ctx: RunContextWrapper[Browser], url: str) -> str:
    """Open a URL in the browser. Returns the page title."""
    page = ctx.context.page
    await page.goto(url, wait_until="domcontentloaded")
    return f"Opened {page.url}. Title: {await page.title()}"


@function_tool
async def read_page(ctx: RunContextWrapper[Browser]) -> str:
    """Return the URL and the visible text of the current page."""
    page = ctx.context.page
    text = await page.inner_text("body")
    return f"URL: {page.url}\n\n{text[:8000]}"


@function_tool
async def click(ctx: RunContextWrapper[Browser], text: str) -> str:
    """Click the first link or button whose visible text contains `text`."""
    page = ctx.context.page
    await page.get_by_text(text).first.click(timeout=10_000)
    await page.wait_for_load_state("domcontentloaded")
    return f"Clicked. Now on {page.url}. Title: {await page.title()}"


@function_tool
async def fill(ctx: RunContextWrapper[Browser], label: str, value: str, press_enter: bool) -> str:
    """Type `value` into the input whose label or placeholder matches `label`."""
    page = ctx.context.page
    box = page.get_by_label(label).or_(page.get_by_placeholder(label)).first
    await box.fill(value, timeout=10_000)
    if press_enter:
        await box.press("Enter")
        await page.wait_for_load_state("domcontentloaded")
    return f"Filled. Now on {page.url}"


agent = Agent[Browser](
    name="Browser agent",
    instructions=(
        "You control a real Chrome browser through tools. "
        "Use read_page to see the page before you click. Answer in one or two sentences."
    ),
    tools=[open_url, read_page, click, fill],
    model="gpt-5.6-luna",
)


async def main():
    browsa = Client(api_key=BROWSA_API_KEY)
    cloud = browsa.create_agent(ttl="5m")  # a Browsa cloud browser, 1 credit
    try:
        async with async_playwright() as p:
            browser = await p.chromium.connect_over_cdp(
                cloud["cdp_http"], headers={"X-API-Key": BROWSA_API_KEY}
            )
            context = browser.contexts[0]
            page = context.pages[0] if context.pages else await context.new_page()

            result = await Runner.run(agent, TASK, context=Browser(page), max_turns=20)
            print(result.final_output)
    finally:
        browsa.destroy_agent(cloud["id"])


if __name__ == "__main__":
    asyncio.run(main())

Run it:

Terminal
python agent.py

The Agents SDK also has a ComputerTool, which takes an AsyncComputer with methods like screenshot, click and type. You can implement one on the same Playwright page; the Claude computer use guide shows the screenshot-and-click mapping. Function tools like the ones above are cheaper, because the model reads text instead of screenshots.

How it works

  1. Connect Playwright. connect_over_cdp(cloud["cdp_http"], headers={"X-API-Key": ...}) attaches to the Browsa browser, and the script uses the tab that is already open.
  2. Expose the page as tools. Each @function_tool is a Playwright call. The page travels in the run context (RunContextWrapper[Browser]), so the model never sees it, only the tools' text results.
  3. Let the agent run. Runner.run() loops: the model calls tools until it can answer, up to max_turns. destroy_agent() in finally shuts the browser down.

Troubleshooting

401 Unauthorized when connecting

The X-API-Key header didn't reach Browsa, or the key is wrong. Check that BROWSA_API_KEY is set in the shell that runs the script and that it starts with agt_; the Browsa SDK refuses keys that don't. Pass the header exactly as the script does.

The connection drops after a few minutes

The browser reached the end of its TTL and shut down. The scripts use ttl="5m" (1 credit). For longer runs, ask for more, up to "1h"; any TTL over 5 minutes costs 5 credits. Start a new browser for the next run.

MaxTurnsExceeded

The agent needed more tool calls than max_turns allows. Raise it, and raise the TTL with it.

A click times out

click matches visible text. Have the agent call read_page first, or add a tool that clicks by CSS selector for pages where text is ambiguous.

Next steps

See the same browser driven by screenshots in the Claude computer use guide, or run browser-use for a ready-made agent.

Run it on a cloud browser.

100 free credits. No card, no subscription.