← Blog

Claude API Browser Automation Agent: Build Guide

2026-10-26 · 5 min read · SubToAPI Team

A browser automation agent built on the Claude API is a program that lets Claude control a real web browser: navigating pages, clicking elements, filling forms, reading content, and taking screenshots, then deciding what to do next based on what it sees. Instead of hardcoding selectors and scripts, you give Claude a set of "browser tools" and let it reason about the page state at each step.

This pattern is useful for tasks that are too variable for traditional scrapers or scripted automation: filling out forms on sites you don't control, QA testing flows across UI changes, extracting data from pages with inconsistent structure, or running multi-step research tasks that involve clicking through several pages. The rest of this guide covers the architecture, the tool definitions you need, and a working example using Playwright.

Why Claude for browser automation

Traditional browser automation (Selenium, Playwright scripts) requires you to know the exact DOM structure in advance. That breaks the moment a site redesigns, and it can't handle pages it's never seen. Claude changes this by closing the loop: the agent reads the current page state (as text, accessibility tree, or screenshot), decides on an action, executes it through a tool call, observes the result, and repeats.

Claude's vision capability is particularly relevant here. You can send a screenshot of the current viewport along with the DOM text, and Claude can identify buttons, form fields, or content even when selectors are messy or dynamically generated.

Core architecture

A browser automation agent has four moving parts:

  1. Browser runtime — Playwright or Puppeteer running headless or headful, controlled server-side.
  2. Tool layer — functions like navigate, click, type, screenshot, extract_text exposed to Claude as tool definitions.
  3. Agent loop — sends the conversation plus tool results back to Claude until it signals it's done.
  4. State capture — screenshots and/or simplified DOM snapshots fed back after every action so Claude can reason about the new page state.

Defining browser tools

Here's a minimal tool schema for a Claude-driven browser agent:

[
  {
    "name": "navigate",
    "description": "Go to a URL in the browser",
    "input_schema": {
      "type": "object",
      "properties": { "url": { "type": "string" } },
      "required": ["url"]
    }
  },
  {
    "name": "click",
    "description": "Click an element by CSS selector",
    "input_schema": {
      "type": "object",
      "properties": { "selector": { "type": "string" } },
      "required": ["selector"]
    }
  },
  {
    "name": "type_text",
    "description": "Type text into an input field",
    "input_schema": {
      "type": "object",
      "properties": {
        "selector": { "type": "string" },
        "text": { "type": "string" }
      },
      "required": ["selector", "text"]
    }
  },
  {
    "name": "screenshot",
    "description": "Capture the current viewport as an image",
    "input_schema": { "type": "object", "properties": {} }
  }
]

The agent loop with Playwright

import { chromium } from "playwright";

async function runStep(toolName, input, page) {
  switch (toolName) {
    case "navigate":
      await page.goto(input.url);
      return "navigated";
    case "click":
      await page.click(input.selector);
      return "clicked";
    case "type_text":
      await page.fill(input.selector, input.text);
      return "typed";
    case "screenshot":
      const buffer = await page.screenshot();
      return buffer.toString("base64");
  }
}

async function agentLoop(task) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  let messages = [{ role: "user", content: task }];

  while (true) {
    const response = await fetch("https://api.subtoapi.app/v1/messages", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        model: "claude-sonnet-4",
        max_tokens: 1024,
        tools: browserTools,
        messages
      })
    }).then(r => r.json());

    const toolUse = response.content.find(b => b.type === "tool_use");
    if (!toolUse) break; // Claude is done

    const result = await runStep(toolUse.name, toolUse.input, page);
    messages.push({ role: "assistant", content: response.content });
    messages.push({
      role: "user",
      content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }]
    });
  }

  await browser.close();
}

This is the same request/response shape as any other Claude tool use workflow — the browser is just the environment the tools act on. If you're new to tool calling in general, see /docs/tools for the full schema reference.

Feeding back page state

The biggest practical challenge isn't the tool calls themselves — it's giving Claude enough context to make good decisions without blowing the context window. A few approaches that work well in combination:

Running this through SubToAPI

If your team is already paying for Claude access through a shared subscription, routing the agent loop above through SubToAPI gives you an API key (sub_live_...) instead of managing raw Claude credentials directly. That matters for browser agents specifically because they tend to be run from backend services or CI pipelines where you want per-service keys, usage visibility, and the ability to revoke access without touching your main account. Streaming support (/docs/streaming) is also useful if you're surfacing agent progress in a UI while it works through a multi-step browser task.

Setup takes the same shape as any other integration — grab a key after /signup, swap the base URL, and the request/response format above works unchanged. Plans start at €9/month on the Solo tier, with Team and Scale tiers adding seats for multiple services or engineers; see /pricing for details.

Practical limits to plan for

Browser automation agents are slower and more expensive per task than text-only agents because every step typically includes a screenshot and a round trip to the model. Budget for:

Start with /docs/quickstart to get a key working end to end, then layer the browser tool loop on top once basic requests are confirmed.

questions

Can Claude directly control a browser without Playwright or Puppeteer? No. Claude doesn't execute code or control hardware — it only returns tool_use requests. You need a browser automation library to actually perform the navigate/click/type actions and report results back.

How many steps can a browser agent take before it gets too expensive? There's no hard limit, but most practical agents cap at 15-30 tool calls per task. Beyond that, costs and latency usually indicate the task should be broken into smaller sub-tasks.

Does Claude need screenshots, or can it work from DOM text alone? Both work, and combining them is often best. DOM text is cheaper and precise for selectors; screenshots help when layout, visual state, or ambiguous elements matter more than exact markup.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →