Hosted browsers
Start a Clearcote browser on our servers with one API call and drive it over the Chrome DevTools Protocol, from Playwright, Puppeteer or any CDP client. Nothing to install or run yourself. You pay per GB of traffic from a prepaid balance.
Pricing
- €1.00 per GB of traffic between the browser and the internet, upload and download combined (1 GB = 109 bytes). Traffic through the managed residential pool is included in that price.
- No charge for time, sessions or CDP messages.
- Prepaid: top up in the dashboard. A browser needs at least €0.50 to start. A running browser is stopped when the balance reaches zero (usage is reported every few seconds, so the last report can take it slightly below zero).
1. Get an API key
Create one on the API keys page. It starts with cc_live_ and is sent as a bearer token. Keep it secret: anyone holding it can spend your balance.
2. Start a browser and connect
POST /api/v1/browsers returns a connectUrl: a single-use WebSocket URL for that browser. Connect within two minutes; it cannot be used twice.
// Node.js + Playwright
import { chromium } from "playwright";
const res = await fetch("https://www.clearcotelabs.com/api/v1/browsers", {
method: "POST",
headers: { authorization: "Bearer cc_live_...", "content-type": "application/json" },
body: JSON.stringify({ fingerprint: "acct-1", platform: "windows", country: "us" }),
});
const { connectUrl, id, error } = await res.json();
if (error) throw new Error(error);
const browser = await chromium.connectOverCDP(connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0] ?? (await context.newPage());
await page.goto("https://example.com");
await browser.close(); // ends the session// Puppeteer: the same connectUrl
const browser = await puppeteer.connect({ browserWSEndpoint: connectUrl });# Python + Playwright
import requests
from playwright.sync_api import sync_playwright
r = requests.post("https://www.clearcotelabs.com/api/v1/browsers",
headers={"authorization": "Bearer cc_live_..."},
json={"fingerprint": "acct-1", "country": "de"}).json()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(r["connectUrl"])
page = browser.contexts[0].new_page()
page.goto("https://example.com")
browser.close()Options
All optional. Send them as the JSON body of the create call.
| Field | Type | Meaning |
|---|---|---|
fingerprint | string | Persona seed. The same seed gives the same device identity every time. |
platform | windows | macos | linux | android | Operating system the persona presents. |
brand | Chrome | Edge | Opera | Vivaldi | Browser brand the persona presents. |
timezone | IANA name | e.g. America/New_York. Use geoip instead to follow the exit IP. |
locale | string | Accept-Language, e.g. en-US,en. |
geoip | boolean | Match timezone and locale to the exit IP. |
proxy | "managed" | { server, username?, password? } | Omitted = managed residential pool. Or your own HTTP proxy. |
country | 2-letter code | Managed pool: exit country, e.g. us, de, gb. |
state | region code | Managed pool: exit state/region, e.g. ca, ny. Needs country. |
city | city name | Managed pool: exit city, e.g. "los angeles". Needs state. |
proxySession | string | Managed pool: sticky label. The same label returns the same exit IP later. |
timeoutSec | number | Hard limit on the session length. |
idleTimeoutSec | number | End the session after this long without a CDP command (10–1800). |
maxGb | number | Stop the session after this much traffic. |
headless | boolean | Default true. |
Exit IPs: rotating, sticky and geo-targeted
- Default: every browser session gets its own residential exit IP and keeps it for the whole session.
- Sticky: pass the same
proxySessionlabel (for example one per account you manage) to get the same exit IP back in a later session. Labels are private to your account. A residential IP stays available while its peer is online, typically several hours; when it goes offline you get another IP from the same network. - Location:
country, then optionallystateandcity. The narrower the target, the smaller the pool. - Your own proxy:
proxy: { server: "http://host:port", username, password }. Only HTTP proxies (CONNECT) for now. Traffic is billed the same way.
Set geoip: true so the browser's timezone and language follow the exit IP.
Managing sessions
# one session: status, traffic, seconds, cost so far
curl -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>
# stop it (a running browser closes within a few seconds)
curl -X DELETE -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>
# balance + your 20 most recent sessions
curl -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsersA session also ends when you close the browser or disconnect, and when a limit below is reached.
Limits
- 5 browsers running or starting at once per account.
- Sessions last at most 4 hours.
- A session with no CDP command for 5 minutes is closed (change it with
idleTimeoutSec). - Every session starts with a fresh profile, which is deleted when the session ends. Keep state (cookies, storage) on your side and restore it over CDP if you need it.
- For safety the browser cannot open local files (
file://), upload files from the server, reach private or internal networks, or send email on port 25.
Errors
| Status | code | Meaning |
|---|---|---|
| 400 | — | An option is invalid; the message says which. |
| 401 | — | Missing, malformed or revoked API key. |
| 402 | INSUFFICIENT_BALANCE | Balance below the minimum. Top up in the dashboard. |
| 429 | CONCURRENCY_LIMIT | Too many browsers running or starting at once. Close one first. |
| 503 | NO_CAPACITY | No free browser slot right now. Retry after a few seconds. |
Errors are JSON: { "error": "...", "code": "..." }. If the WebSocket connection itself is refused, create a new session: connect URLs are single-use and expire after two minutes.