Skip to content

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.

javascript
// 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
javascript
// Puppeteer: the same connectUrl
const browser = await puppeteer.connect({ browserWSEndpoint: connectUrl });
python
# 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.

FieldTypeMeaning
fingerprintstringPersona seed. The same seed gives the same device identity every time.
platformwindows | macos | linux | androidOperating system the persona presents.
brandChrome | Edge | Opera | VivaldiBrowser brand the persona presents.
timezoneIANA namee.g. America/New_York. Use geoip instead to follow the exit IP.
localestringAccept-Language, e.g. en-US,en.
geoipbooleanMatch timezone and locale to the exit IP.
proxy"managed" | { server, username?, password? }Omitted = managed residential pool. Or your own HTTP proxy.
country2-letter codeManaged pool: exit country, e.g. us, de, gb.
stateregion codeManaged pool: exit state/region, e.g. ca, ny. Needs country.
citycity nameManaged pool: exit city, e.g. "los angeles". Needs state.
proxySessionstringManaged pool: sticky label. The same label returns the same exit IP later.
timeoutSecnumberHard limit on the session length.
idleTimeoutSecnumberEnd the session after this long without a CDP command (10–1800).
maxGbnumberStop the session after this much traffic.
headlessbooleanDefault 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 proxySession label (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 optionally state and city. 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

bash
# 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/browsers

A 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

StatuscodeMeaning
400An option is invalid; the message says which.
401Missing, malformed or revoked API key.
402INSUFFICIENT_BALANCEBalance below the minimum. Top up in the dashboard.
429CONCURRENCY_LIMITToo many browsers running or starting at once. Close one first.
503NO_CAPACITYNo 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.