Cloudflare Browser Run · Stateless · Beta
The browser built for machines.
Kitesurf is a stateless, agent-first browser that runs entirely on Cloudflare Workers. It appears for the length of a task — screenshot, extract, PDF, automate — then vanishes. No tabs, no themes, no desktop engine. Just lift.
01 The shift
Humans browse. Agents work.
Every desktop browser is a compromise negotiated for human eyes — tabs, themes, extensions, pixel-perfect rasterization. An agent never asks for any of it. Kitesurf trades away the whole cockpit for the four forces a model actually feels.
“AI cares about token count, context windows, scalability, performance, and costs.” — THE KITESURF DESIGN BRIEF, CLOUDFLARE
Kept · what an agent feels
- Token countA cleaner DOM means less noise shipped into the model's context — and context is the scarcest resource an agent has.
- Context windowsAnswers sized for the window, not the viewport. Nothing renders for eyes that aren't there.
- ScalabilityStateless engines scale like Workers, not like desktops with docks — bursty AI workloads land soft.
- Performance & cost3.1–7.0× less CPU and memory per task. The meter that reads CPU and memory spins that much slower.
Jettisoned · weight released
- Tabs releasedAn agent opens one page at a time.
- Themes releasedNo one is looking.
- Extensions releasedThe agent is the extension.
- Pixel-perfect rendering releasedGood enough to read is good enough to act on.
WEIGHT RELEASED IS WEIGHT YOU DON'T RENT. CHROMIUM KEEPS ALL OF IT FOR THE JOBS THAT NEED IT — ONE PARAMETER AWAY.
02 One task, one engine
It exists for the duration of the work — then it's gone.
A Kitesurf engine is an ephemeral, fully isolated V8 isolate that materializes for a single task. Navigate, extract, screenshot, render a PDF — then every trace dissolves. Nothing warm to pay for between tasks. Nothing left to compromise between sessions.
Standing engines 1
Stateless wherever possible
Only the Engine keeps state — bookkeeping for the CDP socket. Everything a page touches — PageScript, PageRenderer, SandboxOutbound — is stateless, retryable, and safe to kill mid-render.
Every page load is untrusted input
Every session starts fresh. Pages run in isolated V8 isolates, and the network exits through one sandboxed door that enforces CORS, shapes browser headers, and keeps per-page cookie jars.
Failure degrades, never dies
Any failure falls back to a blank frame or a missing element — never a dead session. An agent gets an honest error, not a hung tab.
UNDER THE WING
CHROMIUM DOCKS A FULL DESKTOP ENGINE PER SESSION. KITESURF WEIGHS NOTHING AT REST — THERE IS NO REST STATE.
03 Proof, not promises
The metrics that drive your bill.
Cloudflare ran both engines through the same quick actions — same 14-URL corpus, five runs each, medians below. CPU and memory are what the invoice reads. Wall time is where Chromium keeps an honest edge.
CPU per task
milliseconds3.1× less CPUCompute actually burned to complete one quick action — screenshot or extraction. This is the line the bill reads.
Memory footprint
MiB4.7× less memoryResident set per engine. 7.0× on HTML extraction (39.4 vs 273.7 MiB) — scale that by concurrency and watch the fleet fit.
Wall time
milliseconds1.8× slower · honest edgeChromium's warm JIT and hot raster pipeline win the clock — the gap is rasterization and PNG/JPEG encode. Kitesurf concedes the stopwatch and wins the meter. You rent CPU and memory, not milliseconds.
The runway — cumulative CPU across tasks
AT 100 SCREENSHOTS
MEDIANS OF FIVE QUICK-ACTION RUNS OVER A 14-URL CORPUS (kitesurf.cloudflare.app/corpus.txt). CHROMIUM MEASURED FROM A WARM POOL; KITESURF COLD — AS EACH RUNS IN PRODUCTION. PUBLISHED BY CLOUDFLARE.
04 Standards
An engine you can measure.
Kitesurf passes over 235,000 Web Platform Tests subtests — and climbs by hundreds every week. The panel reads coverage where agents actually live: reading, selecting, requesting, resolving.
DOM · HTML · SELECTION · SVG
ENCODING · CORS · XHR · URL
Hover or focus a gauge — each reading is the share of subtests passing in that area. Amber means still climbing.
Field log — rendered in the wild
A REAL CHECKER, NOT A CLAIM: RUN YOUR OWN SITES IN THE PLAYGROUND — IT SHIPS WITH DEVTOOLS FOR DOM, CONSOLE, NETWORK, EVEN WASM MEMORY.
05 Flown in daylight
What it won't do.
A kite is honest about the wind. These limits are engineering decisions, stated in daylight — not fine print. If your work needs them, Chromium stays the right tool, one parameter away on the same API.
No video playback
Kitesurf rasterizes pages, not codecs. If your agent's job is to watch, point it at Chromium Browser Run.
No WebGL
There is no GPU inside an isolate. 3D scenes won't draw — and screenshots of them won't either.
No bot-challenge TLS fingerprints
It will not handshake as desktop Chrome against fingerprinting bot-walls. It was never going to lie about being Chromium — it isn't.
No long-running authenticated sessions
Stateless means stateless: nothing persists between tasks. Durable, multi-hour, logged-in flows belong to a warm browser.
Rendering isn't pixel-perfect either — CSS parsing can be slightly off, and a subset of CDP is implemented. Agents will be just fine. Test your own sites in the playground; if they need the whole desktop engine, keep Chromium.
KITESURF IS IN BETA AND FREE DURING IT — SUBJECT TO PER-ACCOUNT LIMITS.
06 One parameter
Everything you already run, re-rigged in one line.
No new SDK. Append browser=kitesurf to any Browser Run endpoint — Quick Action, CDP socket, or MCP bridge — and the same Puppeteer, Playwright, or MCP client you already have flies a lighter wing.
# one screenshot, one engine, gone when it lands
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/screenshot?browser=kitesurf' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{ "url": "https://example.com" }' \
--output "screenshot.png"
One query parameter. browser=kitesurf works on any Browser Run quick action — screenshot, content, PDF, scrape. Omit it and you get Chromium. Existing scripts keep working either way.
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/screenshot?browser=kitesurf' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{ "url": "https://example.com" }' \
--output "screenshot.png"
// npm i puppeteer-core — your existing script, one parameter changed
const puppeteer = require("puppeteer-core");
const ACCOUNT_ID = process.env.CF_ACCOUNT_ID;
const API_TOKEN = process.env.CF_API_TOKEN;
// vvv the only line that changes vvv
const browserWSEndpoint =
`wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}` +
`/browser-run/devtools/browser?browser=kitesurf`;
async function main() {
const browser = await puppeteer.connect({
browserWSEndpoint,
headers: { Authorization: `Bearer ${API_TOKEN}` },
});
const page = await browser.newPage();
await page.goto("https://news.ycombinator.com");
console.log(await page.title()); // "Hacker News"
await page.screenshot({ path: "hn.png" });
await browser.close(); // isolate freed — nothing remains
}
main().catch(console.error);
Drop-in CDP. The websocket takes ?browser=kitesurf and everything else — selectors, waits, clicks, screenshots — runs unchanged. Playwright and chrome-remote-interface connect through the same socket.
// npm i puppeteer-core — your existing script, one parameter changed
const puppeteer = require("puppeteer-core");
const ACCOUNT_ID = process.env.CF_ACCOUNT_ID;
const API_TOKEN = process.env.CF_API_TOKEN;
// vvv the only line that changes vvv
const browserWSEndpoint =
`wss://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}` +
`/browser-run/devtools/browser?browser=kitesurf`;
async function main() {
const browser = await puppeteer.connect({
browserWSEndpoint,
headers: { Authorization: `Bearer ${API_TOKEN}` },
});
const page = await browser.newPage();
await page.goto("https://news.ycombinator.com");
console.log(await page.title()); // "Hacker News"
await page.screenshot({ path: "hn.png" });
await browser.close(); // isolate freed — nothing remains
}
main().catch(console.error);
{
"mcp": {
"kitesurf": {
"type": "local",
"command": [
"npx", "-y", "chrome-devtools-mcp@latest",
"--wsEndpoint=wss://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/devtools/browser?browser=kitesurf",
"--wsHeaders={\"Authorization\":\"Bearer <API_TOKEN>\"}"
],
"enabled": true
}
}
}
Any MCP client that speaks CDP. chrome-devtools-mcp bridges your agent to the same websocket — navigate, click, evaluate — now riding isolates on Workers.
{
"mcp": {
"kitesurf": {
"type": "local",
"command": [
"npx", "-y", "chrome-devtools-mcp@latest",
"--wsEndpoint=wss://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/devtools/browser?browser=kitesurf",
"--wsHeaders={\"Authorization\":\"Bearer <API_TOKEN>\"}"
],
"enabled": true
}
}
}
07 Upwind
The wind is free right now.
Kitesurf is in beta and free to fly — per-account limits apply. Take it into the playground, read the docs, then re-rig your stack with one parameter.
kitesurf.cloudflare.app · beta · free