Your AI agent needs to talk to a browser API. You have two architectural options for that API: REST or GraphQL. Both can get the job done, but they make different tradeoffs on payload size, round trips, caching, and developer experience. For browser automation specifically, one of these tradeoffs matters more than the others.
This is not an abstract API design debate. It is a practical evaluation of which style works better when your client is an LLM-powered agent making sequential browser commands, and your server is managing stateful browser sessions.
In this guide, you'll learn:
- How REST and GraphQL differ in the context of browser automation and agent workflows
- Why Browserbeam chose a REST/JSON API with a step-based execution model
- Side-by-side request and response comparisons for the same browser task
- Latency, payload size, and caching tradeoffs for each API style
- How to wrap Browserbeam's REST API behind a GraphQL layer for multi-source agents
- A decision framework for choosing REST vs GraphQL based on your agent type
- Common API architecture mistakes that slow down agent workflows
TL;DR: REST wins for browser automation APIs. Browser sessions are stateful, commands are sequential, and each step changes the server state. GraphQL's strengths (flexible queries, reduced over-fetching, single-endpoint access) add value when aggregating data from multiple sources, not when issuing sequential commands to a single browser session. Browserbeam uses a REST API with a step-based execution model that maps naturally to the observe-act-extract loop of AI agents.
API Styles for Scraping: GraphQL vs REST
REST and GraphQL are two approaches to API design. They are not interchangeable; each optimizes for different access patterns.
REST: Resource-Based, Action-Oriented
REST organizes APIs around resources and HTTP methods. You create a session (POST /sessions), act on it (POST /sessions/:id/act), and eventually close it. Each endpoint has a single responsibility, and the server controls what data comes back.
Key characteristics:
- Resources identified by URLs
- HTTP methods (GET, POST, PUT, DELETE) define operations
- Server determines response shape
- Stateless requests (session state lives server-side)
- Cacheable by default (GET requests via HTTP caching headers)
GraphQL: Query-Based, Client-Controlled
GraphQL exposes a single endpoint where the client specifies exactly what fields it wants. The client sends a query (or mutation), and the server returns only the requested data. This eliminates over-fetching (getting fields you don't need) and under-fetching (needing multiple round trips).
Key characteristics:
- Single endpoint for all operations
- Client specifies response shape via queries
- Strong type system with introspection
- Mutations for write operations
- Subscriptions for real-time data
The Fundamental Difference for Agent Workflows
For agent workflows, the key question is: does the client need flexible queries across diverse data, or does it need to issue sequential commands against a stateful resource?
| Access Pattern | Better Fit | Why |
|---|---|---|
| Sequential browser commands | REST | Each command changes state; response shape is consistent |
| Aggregating data from multiple sources | GraphQL | Client picks fields from multiple types in one query |
| Real-time page monitoring | REST + webhooks or GraphQL subscriptions | Both work; REST is simpler |
| Batch data extraction | REST | Declarative schema handles field selection server-side |
| Dynamic dashboards pulling agent data | GraphQL | Flexible queries avoid building custom endpoints |
Browserbeam's REST/JSON Approach Explained
Browserbeam's API uses two core REST endpoints that handle every browser interaction:
| Endpoint | Method | Purpose |
|---|---|---|
/v1/sessions |
POST | Create a browser session (optionally navigate to a URL) |
/v1/sessions/:id/act |
POST | Execute one or more steps on the session |
/v1/sessions/:id |
GET | Check session status |
/v1/sessions |
GET | List active sessions |
/v1/sessions/:id |
DELETE | Destroy a session |
The act endpoint is where the design gets interesting. Instead of one endpoint per action (click, fill, extract), Browserbeam uses a step-based model. You send an array of steps, and the server executes them sequentially:
{
"steps": [
{"observe": {}},
{"click": {"ref": "e3"}},
{"extract": {"title": "h1 >> text", "price": ".price >> text"}}
]
}
This design has three advantages for agent workflows:
Reduced round trips. An agent that needs to observe, click, and extract can do it in one HTTP call instead of three. For latency-sensitive workflows, this can cut response time in half.
Atomic execution. The steps run in order on the same session state. There is no risk of another request modifying the session between your click and your extract.
Consistent response format. Every
actcall returns the sameSessionEnvelopestructure regardless of what steps you ran. Your agent always knows what to expect.
Why Not GraphQL?
A GraphQL version of this API would look something like:
mutation {
act(sessionId: "ses_abc123", steps: [
{ observe: {} },
{ click: { ref: "e3" } },
{ extract: { schema: "{ title: \"h1 >> text\" }" } }
]) {
page {
url
title
stable
markdown { content }
interactiveElements { ref tag role label }
}
extraction
error { message code }
}
}
This works, but it does not add value. The client always wants the full session state after acting. There is no over-fetching problem because the response is already structured and compact. The SessionEnvelope is 500-2,000 tokens of structured data, not a bloated payload with unnecessary fields.
GraphQL's strength is letting clients select exactly the fields they need from a complex data graph. Browserbeam's responses are already lean. Adding a query layer on top of an already-optimized response is overhead with no benefit.
Side-by-Side Request Examples
REST Request to Browserbeam
Create a session and extract book data in two API calls:
# Step 1: Create session and navigate
SESSION=$(curl -s -X POST https://api.browserbeam.com/v1/sessions \
-H "Authorization: Bearer $BROWSERBEAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://books.toscrape.com"}' | jq -r '.session_id')
# Step 2: Extract data
curl -X POST "https://api.browserbeam.com/v1/sessions/$SESSION/act" \
-H "Authorization: Bearer $BROWSERBEAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"steps": [{
"extract": {
"books": [{
"_parent": "article.product_pod",
"_limit": 5,
"title": "h3 a >> text",
"price": ".price_color >> text"
}]
}
}, {"close": {}}]
}'
from browserbeam import Browserbeam
client = Browserbeam()
session = client.sessions.create(url="https://books.toscrape.com")
result = session.extract(
books=[{"_parent": "article.product_pod", "_limit": 5,
"title": "h3 a >> text", "price": ".price_color >> text"}]
)
for book in result.extraction["books"]:
print(f"{book['title']}: {book['price']}")
session.close()
import Browserbeam from "@browserbeam/sdk";
const client = new Browserbeam();
const session = await client.sessions.create({ url: "https://books.toscrape.com" });
const result = await session.extract({
books: [{ _parent: "article.product_pod", _limit: 5,
title: "h3 a >> text", price: ".price_color >> text" }]
});
for (const book of result.extraction.books) {
console.log(`${book.title}: ${book.price}`);
}
await session.close();
require "browserbeam"
client = Browserbeam::Client.new
session = client.sessions.create(url: "https://books.toscrape.com")
result = session.extract(
books: [{ "_parent" => "article.product_pod", "_limit" => 5,
"title" => "h3 a >> text", "price" => ".price_color >> text" }]
)
result.extraction["books"].each do |book|
puts "#{book['title']}: #{book['price']}"
end
session.close
Two HTTP calls total. The SDK wraps them into clean method calls, but under the hood it is POST /v1/sessions followed by POST /v1/sessions/:id/act. The response is always the same SessionEnvelope shape.
Equivalent GraphQL Query Pattern
If Browserbeam used GraphQL instead, the same task would look like this:
# Mutation 1: Create session
mutation {
createSession(url: "https://books.toscrape.com") {
sessionId
page { url title stable }
}
}
# Mutation 2: Extract data
mutation {
act(sessionId: "ses_abc123", steps: [
{ extract: {
schema: "{\"books\": [{\"_parent\": \"article.product_pod\", \"_limit\": 5, \"title\": \"h3 a >> text\", \"price\": \".price_color >> text\"}]}"
}},
{ close: {} }
]) {
extraction
page { url title }
}
}
The query is longer. The response contains the same data. The only potential advantage would be field selection (requesting just extraction without page), but that saves a trivial amount of bandwidth on an already-compact response.
Response Format Comparison
Both styles return the same core data. Here is what the extraction response looks like:
{
"session_id": "ses_abc123",
"expires_at": "2026-04-04T12:30:00Z",
"request_id": "req_xyz789",
"completed": 2,
"page": {
"url": "https://books.toscrape.com/",
"title": "All products | Books to Scrape - Sandbox",
"stable": true
},
"extraction": {
"books": [
{"title": "A Light in the ...", "price": "£51.77"},
{"title": "Tipping the Velvet", "price": "£53.74"},
{"title": "Soumission", "price": "£50.10"},
{"title": "Sharp Objects", "price": "£47.82"},
{"title": "Sapiens: A Brief History ...", "price": "£54.23"}
]
},
"media": [],
"blockers_dismissed": [],
"error": null
}
With GraphQL, you could omit media and blockers_dismissed from the response. That saves approximately 30 bytes. On a response that is already under 1 KB, this optimization is not meaningful.
Performance Comparison for Agent Workflows
Latency and Round-Trip Analysis
For browser automation, latency is dominated by browser execution time (page load, rendering, JavaScript execution), not API overhead. A typical Browserbeam request takes 2-5 seconds, of which the HTTP layer contributes 20-50ms.
| Component | REST Overhead | GraphQL Overhead |
|---|---|---|
| HTTP connection | Same (both use HTTPS) | Same |
| Request parsing | JSON parse (~1ms) | Query parse + validation (~3-5ms) |
| Response serialization | JSON serialize (~1ms) | Field resolution + serialize (~2-4ms) |
| Total API overhead | ~2ms | ~5-9ms |
| Browser execution | 2,000-5,000ms | Same (not affected by API style) |
The API overhead difference is negligible compared to browser execution time. Choosing GraphQL or REST for performance reasons in browser automation is optimizing the wrong thing.
Where round trips matter more is multi-step workflows. Browserbeam's step batching (sending multiple steps in one act call) already minimizes round trips regardless of API style. A REST API with step batching makes fewer round trips than a naive GraphQL implementation that sends one mutation per action.
Payload Size Differences
| Scenario | REST Payload | GraphQL Payload | Savings |
|---|---|---|---|
| Create session request | ~50 bytes | ~120 bytes (query + variables) | REST smaller |
| Extract request | ~180 bytes | ~350 bytes (mutation + variables) | REST smaller |
| Full session response | ~800 bytes | ~600 bytes (selected fields) | GraphQL smaller |
| Extract-only response | ~400 bytes | ~350 bytes | Negligible |
GraphQL requests are larger because the query language itself adds overhead. GraphQL responses can be smaller because of field selection. For browser automation, both differences are in the hundreds of bytes range. Neither matters at this scale.
For context: a single page of structured markdown output is 2-8 KB. The API protocol overhead is a rounding error.
Caching and CDN Compatibility
REST has a natural caching advantage. GET /v1/sessions/:id can use standard HTTP caching headers (ETag, Cache-Control, Last-Modified). CDNs, browser caches, and HTTP intermediaries understand REST caching natively.
GraphQL uses POST for all requests, which means HTTP caches do not work by default. You can implement application-level caching (Persisted Queries, CDN query caching via GET with query parameters), but it requires additional infrastructure.
For browser automation, caching is less relevant because every session is unique and every action changes state. But if you are building a data layer on top of browser extraction results (caching extracted product prices, for example), REST's native HTTP caching is simpler to implement.
Example: Using Browserbeam in a GraphQL Workflow
The most practical GraphQL integration is not replacing Browserbeam's REST API with GraphQL. It is wrapping Browserbeam as a data source inside your own GraphQL API.
If your agent aggregates data from multiple sources (a browser session, a database, a third-party API), a GraphQL layer lets you unify them behind a single query interface:
import Browserbeam from "@browserbeam/sdk";
const resolvers = {
Query: {
productData: async (_: unknown, { url }: { url: string }) => {
const client = new Browserbeam();
const session = await client.sessions.create({ url });
const result = await session.extract({
products: [{ _parent: "article.product_pod", _limit: 10,
title: "h3 a >> text", price: ".price_color >> text" }]
});
await session.close();
return result.extraction.products;
}
},
Product: {
title: (product: Record) => product.title,
price: (product: Record) => product.price
}
};
Your GraphQL schema:
type Product {
title: String!
price: String!
}
type Query {
productData(url: String!): [Product!]!
}
Now a client can query:
query {
productData(url: "https://books.toscrape.com") {
title
price
}
}
This pattern makes sense when your application already has a GraphQL API and you want to add browser-extracted data as another data source. It does not make sense if Browserbeam is your only data source, because the GraphQL layer adds complexity without reducing round trips or payload size.
Real-World Decision Scenarios
When REST Wins for Browser Automation
REST is the better choice when:
- Your agent talks to one browser API. A single-purpose scraping agent or web automation agent does not benefit from GraphQL's multi-source query flexibility.
- Commands are sequential and stateful. Browser automation is inherently sequential: navigate, wait, click, extract. REST's resource-based model maps directly to this pattern.
- You want simple error handling. REST returns HTTP status codes (429 for rate limits, 404 for expired sessions, 422 for invalid requests). GraphQL returns 200 for everything and puts errors in the response body, which requires custom error handling logic.
- You need standard tooling. cURL, Postman, Insomnia, and every HTTP library work with REST out of the box. GraphQL requires a client library or manual query construction.
When GraphQL Wins for Data Aggregation
GraphQL is the better choice when:
- Your agent queries multiple data sources. A research agent that pulls data from a browser session, a database, and three third-party APIs benefits from a unified query interface.
- Downstream consumers need flexible field selection. If multiple frontend clients consume the same agent output but need different fields, GraphQL prevents building custom REST endpoints for each client.
- You are building a data aggregation layer. If browser-extracted data is one input among many, wrapping it in a GraphQL resolver keeps your API surface clean.
- Real-time subscriptions matter. If your application needs live updates when browser extraction completes, GraphQL subscriptions provide a native mechanism. (REST can achieve this with webhooks or Server-Sent Events, but it requires more custom code.)
Hybrid Approaches
Many production systems use both. The agent talks to Browserbeam's REST API directly for browser operations, and a GraphQL layer aggregates the results with other data sources for downstream consumers.
| Layer | Protocol | Purpose |
|---|---|---|
| Agent to Browserbeam | REST | Browser commands (create, observe, act, extract, close) |
| Agent to other APIs | REST or GraphQL | Third-party data (pricing databases, CRMs, analytics) |
| Agent output to application | GraphQL | Unified query interface for frontends and dashboards |
This hybrid approach gives you the simplicity of REST for browser operations and the flexibility of GraphQL for data aggregation. Neither layer fights against its natural strengths.
When to Use Each (with AI Agents in Mind)
AI agents add a specific constraint: the LLM making decisions needs a predictable, parseable API contract. Here is how that affects the REST vs GraphQL choice.
LLM Function Calling with REST
Most LLM function calling frameworks (OpenAI function calling, Anthropic tool use, LangChain tools) expect function signatures with named parameters and typed returns. Browserbeam's REST API maps directly to this:
tools = [
{
"type": "function",
"function": {
"name": "extract_data",
"description": "Extract structured data from the current page",
"parameters": {
"type": "object",
"properties": {
"schema": {
"type": "object",
"description": "Extraction schema with CSS selectors"
}
},
"required": ["schema"]
}
}
}
]
The LLM calls extract_data with a schema, your code calls session.extract(**schema), and the result flows back to the LLM. Straightforward, no query construction needed.
LLM Function Calling with GraphQL
With a GraphQL API, the LLM would need to construct a query string:
tools = [
{
"type": "function",
"function": {
"name": "graphql_query",
"description": "Execute a GraphQL mutation on the browser session",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "GraphQL mutation string"
},
"variables": {
"type": "object",
"description": "Query variables"
}
},
"required": ["query"]
}
}
}
]
Now the LLM needs to generate valid GraphQL syntax. That is an unnecessary source of errors. LLMs are better at filling in structured parameters (JSON objects) than generating query language strings.
Token Efficiency
REST API calls produce predictable, compact prompts for the LLM. A function call with named parameters is 50-100 tokens. A GraphQL query string with field selection, variable references, and fragment syntax is 150-300 tokens for the same operation.
Over a 20-step agent workflow, REST saves 2,000-4,000 tokens just on API call formatting. Combined with Browserbeam's structured output that reduces page content to 500-2,000 tokens per observation, the total token savings add up fast.
Common API Architecture Mistakes
Over-Engineering with GraphQL for Simple Tasks
If your agent does one thing (scrape a site, monitor prices, fill forms), a GraphQL layer adds a schema to maintain, resolvers to write, and a query language to debug. REST endpoints with the Python SDK or TypeScript SDK get you from zero to working agent in minutes, not hours.
GraphQL pays for itself when you have 5+ data sources, multiple client types, and a team that maintains the schema. For a single browser automation integration, it is overhead.
Under-Fetching with REST (N+1 Problem)
The classic REST weakness: you need session info, page state, and extraction results, so you make three separate GET requests. Browserbeam avoids this by design. The act endpoint returns the complete session state after every step. One call, all data. No N+1 problem.
If you are building a REST API on top of browser results and your clients need different slices of data, consider adding query parameters (?include=page,extraction) rather than building a full GraphQL layer. The Browserbeam API docs show how the observe endpoint supports optional includes for page maps, links, and markdown content.
Ignoring Pagination in Agent Workflows
When your agent extracts lists (products, articles, search results), the list can span multiple pages. Neither REST nor GraphQL solves pagination automatically. You need to handle it in your agent logic.
Browserbeam's approach: use the _limit parameter in extraction schemas for page-level limits, and build pagination logic in your agent loop (click "next page", extract, repeat). For pages with infinite scroll, the scroll_collect endpoint handles it in a single call.
Not Versioning Your API Integration
Browserbeam's API is versioned (/v1/sessions). If you build a GraphQL wrapper on top, version your schema too. Breaking changes in your GraphQL schema can cascade to every client that queries it.
Best practice: pin your Browserbeam SDK version in requirements.txt or package.json, and test against API changes before deploying. The REST API's versioned URL path makes this explicit. GraphQL APIs often use schema evolution without explicit versioning, which can cause subtle breakages.
Skipping Error Handling for API Responses
REST APIs return HTTP status codes that map to specific error conditions:
| Status Code | Meaning | Agent Action |
|---|---|---|
| 200 | Success | Process response |
| 401 | Invalid API key | Stop, check configuration |
| 404 | Session expired or not found | Create a new session |
| 422 | Invalid request (bad step, missing field) | Fix the request |
| 429 | Rate limited | Wait and retry with backoff |
| 503 | Service temporarily unavailable | Retry with backoff |
GraphQL returns 200 for everything. Errors appear in the response body under an errors array. Your agent needs custom logic to parse error types, extract error codes, and decide whether to retry.
For autonomous agents that run without human supervision, explicit HTTP status codes are easier to handle programmatically. The Browserbeam Python SDK raises specific exceptions (RateLimitError, SessionNotFoundError, QuotaExceededError) that map directly to these status codes.
Decision Framework by Agent Type
Single-Purpose Scraping Agents
Use REST. A scraping agent that navigates pages and extracts structured data has a linear workflow: create session, navigate, extract, close. REST's sequential request model matches this exactly.
Browserbeam's declarative extraction schemas handle the data selection that GraphQL would otherwise provide. You specify what fields you want in the schema, and the API returns just those fields as JSON. This is GraphQL-style field selection at the extraction level, without the query language overhead.
Multi-Source Research Agents
Use REST for browser operations, GraphQL for aggregation. A research agent that browses the web, queries a database, and calls a news API benefits from a GraphQL layer that unifies these data sources.
The browser operation stays REST (Browserbeam SDK), but the agent's output feeds into a GraphQL API that downstream consumers query. This hybrid approach is the most common pattern at companies running multi-step agent workflows.
Real-Time Monitoring Agents
Use REST with webhooks or polling. A monitoring agent that checks page content every 5 minutes does not need GraphQL subscriptions. A cron job that calls the Browserbeam API, compares results to the previous run, and sends alerts is simpler to build and debug.
If your monitoring system already uses GraphQL subscriptions for other data sources, wrapping Browserbeam results into a subscription resolver keeps the interface consistent. But do not introduce GraphQL subscriptions just for browser monitoring.
Frequently Asked Questions
Should I use GraphQL or REST for web scraping?
REST is the better fit for web scraping. Scraping workflows are sequential (navigate, wait, extract) and stateful (each action changes the page). REST's resource-based model maps directly to this pattern. GraphQL adds query language overhead without reducing round trips or payload size for browser automation tasks.
Does Browserbeam support GraphQL?
Browserbeam uses a REST/JSON API, not GraphQL. The API has two main endpoints: POST /v1/sessions (create) and POST /v1/sessions/:id/act (execute steps). You can wrap these in a GraphQL resolver if your application already uses a GraphQL API, but the native interface is REST.
Is GraphQL faster than REST for API calls?
For browser automation, neither is meaningfully faster. API protocol overhead (2-9ms) is negligible compared to browser execution time (2-5 seconds per step). GraphQL can reduce payload size through field selection, but Browserbeam's REST responses are already compact (under 1 KB for most requests). The performance difference is not detectable in practice.
Can I use Browserbeam with a GraphQL API gateway?
Yes. Browserbeam's REST API works behind any API gateway (Kong, AWS API Gateway, Apollo Federation). Create a GraphQL resolver that calls the Browserbeam SDK, and expose the results through your existing GraphQL schema. This is the recommended pattern for teams that already have a GraphQL API.
What is the N+1 problem with REST APIs?
The N+1 problem occurs when a client needs related data that requires multiple sequential requests. For example, fetching a list of sessions (1 request) then fetching details for each session (N requests). Browserbeam avoids this by returning the complete session state in every response. The act endpoint returns page state, extraction results, and error information in a single call.
When should I use GraphQL instead of REST?
Use GraphQL when your application queries multiple data sources with different schemas, when multiple client types need different field combinations from the same data, or when you need real-time subscriptions. For single-API integrations like browser automation, REST is simpler and equally capable.
How do LLMs interact with REST vs GraphQL APIs?
LLMs interact with APIs through function calling (tools). REST APIs map cleanly to function parameters: each endpoint becomes a function with typed arguments. GraphQL requires the LLM to generate query strings, which adds token overhead and a source of syntax errors. Most LLM frameworks (OpenAI function calling, Anthropic tool use) are designed around the REST pattern.
What API style does Browserbeam use for its MCP server?
Browserbeam's MCP server exposes the REST API as MCP tools. Each tool (create session, observe, click, extract, close) maps to a REST endpoint. The MCP protocol itself uses JSON-RPC, but the underlying browser operations go through the REST API. This keeps the integration consistent whether you use the SDK, the REST API, or the MCP server.
Conclusion
GraphQL and REST solve different problems. For browser automation APIs, REST wins on simplicity, error handling, LLM compatibility, and caching. GraphQL wins when you need flexible queries across multiple data sources or real-time subscriptions.
Browserbeam chose REST for a reason: browser sessions are stateful resources with sequential operations. The step-based act endpoint handles command batching. Declarative extraction schemas handle field selection. Structured responses handle over-fetching. These are the problems GraphQL solves, and Browserbeam solves them at the application level without a query language layer.
If you are building a single-purpose agent, use Browserbeam's REST API directly. If you are building a multi-source platform, wrap Browserbeam in a GraphQL resolver alongside your other data sources. Either way, the browser operations themselves are REST.
Start with the API docs to see the full endpoint reference. The Python SDK guide shows every method, and the web scraping agent tutorial walks through a complete agent build from first API call to production deployment.