Migrating from Browserless
Browserless gives you a WebSocket address to connect Puppeteer or Playwright to, in its cloud or in its Docker image. Clearcote does both too, so the change is the address your code connects to. The page code stays.
From Browserless cloud
A Browserless address is fixed. A Clearcote session is created first, and its connectUrl is used once, so the one new step is a create call before each connection. The SDK does both in one call.
import puppeteer from "puppeteer-core";
async function connectUrl(options = {}) {
const res = await fetch("https://www.clearcotelabs.com/api/v1/browsers", {
method: "POST",
headers: { authorization: "Bearer " + process.env.CLEARCOTE_API_KEY, "content-type": "application/json" },
body: JSON.stringify(options),
});
const { connectUrl, error } = await res.json();
if (error) throw new Error(error);
return connectUrl; // single-use: create one per connection
}
const browser = await puppeteer.connect({
browserWSEndpoint: "wss://production-sfo.browserless.io?token=" + process.env.BROWSERLESS_TOKEN,
browserWSEndpoint: await connectUrl({ identity: "acct-1", country: "us" }),
defaultViewport: null,
});
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.close(); // ends the sessionGet the key on the API keys page; the SDK reads it from CLEARCOTE_API_KEY. The browser starts as you connect, usually within a few seconds; with a country it can take up to about half a minute, so give Playwright's connectOverCDP a timeout of two minutes. Keep defaultViewport: null so Puppeteer keeps the window size the persona gave the browser.
Where the URL options go
Settings you put in the Browserless URL go into the create call's JSON body. Chromium launch flags have no hosted equivalent: the persona and its switches are set for you.
| You want | Create body |
|---|---|
| a residential exit in a country | "country": "de" (also state, city); the managed pool is the default |
| your own proxy | "proxy": { "server": "http://host:8080", "username": "…", "password": "…" } |
| the same IP and device for an account | "identity": "acct-1" |
| cookies kept between sessions | "profile": { "name": "acct-1", "persist": true } |
| a session time limit | "timeoutSec", "idleTimeoutSec" |
| a recording | "record": true |
| to reconnect later | "keepAlive": true |
Every option is in the options reference. Stealth is not a separate route: every hosted browser runs the engine-level persona, and checkboxes, sliders and press-and-hold verifications are handled by default (Challenges). There are no /content, /screenshot or /pdf endpoints: write those as a few lines of Playwright, or describe the task and start an agent run, which returns JSON.
From the Browserless Docker image
clearcote serve is the closest match to the open-source image: one standing endpoint, a separate browser per identity, the settings in the connection URL.
docker run -p 3000:3000 -e TOKEN=... ghcr.io/browserless/chromium
pip install -U clearcote # or: npm install -g clearcote
clearcote serve --port 9222 --idle-timeout 300 --max-browsers 16p.chromium.connect_over_cdp("http://127.0.0.1:9222") # one shared browser
p.chromium.connect_over_cdp("http://127.0.0.1:9222?fingerprint=acct-1&platform=windows") # a browser of its own
p.chromium.connect_over_cdp("http://127.0.0.1:9222?fingerprint=acct-2&proxy=http://user:pass@host:8080&geoip=true")For one identity per container, the teamflatearth/clearcote image serves CDP on port 9222; see Deployment. Either way, keep the port on networks you trust: a CDP port is full control of the browser. Several browsers at once, and proxies with a password, need a licence key: free with a GitHub sign-in for one browser at a time, and Pro for up to 250.
What it costs
Hosted browsers are paid per GB of traffic from a prepaid balance, with no plan to pick. See Pricing & traffic, and Limits & errors for concurrency.