cdpfleet Docs Sign up free

Examples

Every example is a complete program. Set CDPFLEET_API_KEY, replace the placeholder proxy with yours, install the client named at the top, and run it.

Google Chrome, beta channel

Run the next Chrome release before it ships. Use "dev" for the dev channel.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    channel: 'beta',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Pin an older Chrome major (156)

Run a previous major to reproduce or compare behaviour. Previous majors rotate as new versions ship, so check the Chrome page for the current list rather than hard-coding one.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    version: '156',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with a Windows fingerprint

Camoufox builds a consistent identity at launch. Here: Windows, US English, human-like mouse movement, WebRTC disabled. Replace the proxy with yours; Camoufox also matches its location to it.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    os: 'windows',
    locale: 'en-US',
    humanize: true,
    block_webrtc: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Proxy pool with per-host rules

Spread traffic over two residential exits and send CDN assets through a datacenter pool. See Proxies.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    headless: 'new',
    proxy: [
      'http://u:[email protected]:8080',
      'http://u:[email protected]:8080',
    ],
    proxy_rules: [
      {
        hosts: [
          '*.cloudfront.net',
        ],
        proxy: 'http://u:[email protected]:4444',
      },
    ],
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

SOCKS5 proxy with authentication

Any socks5:// proxy works like an HTTP one, including username and password. Use socks5h:// if the proxy should resolve hostnames.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'socks5://user:[email protected]:1080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Patchright, headful, for stricter sites

A stealth-patched Chromium with a real display (2 threads): no HeadlessChrome anywhere, a normal window, and Playwright's automation flags removed. Patchright disables console events by design; see Camoufox vs Patchright.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/patchright/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox: pinned version, macOS persona, no images

A reproducible Firefox 150 identity that reports macOS, matches its timezone and locale to the proxy, and skips images to save proxy bandwidth. main_world_eval lets your checks read the page's own world (mw: prefix).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    version: '150',
    os: 'macos',
    geoip: true,
    block_images: true,
    main_world_eval: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Short-lived sessions for burst jobs

For many small jobs, make each session end itself: 15 seconds idle or 2 minutes total, whichever comes first, so a hung script never bills for long. Values take s, m, h.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    inactivity_timeout: '15s',
    overall_timeout: '2m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with a fixed screen and window

Pin the persona's screen to common laptop sizes and the window to 1366×768 so screenshots and layouts are the same on every run, while the rest of the identity is still generated.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'windows',
    screen: {
      minWidth: 1366,
      maxWidth: 1920,
      minHeight: 768,
      maxHeight: 1080,
    },
    window: [
      1366,
      768,
    ],
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Rebrowser with the Playwright 1.52 client

Rebrowser is a patched Chromium that needs the 1.52 Playwright client — the generator picks it for you. Headful by default (2 threads); see stealth builds for how it compares.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/rebrowser/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Microsoft Edge, beta channel

A real Edge with the Edg/ user-agent token and the Microsoft Edge client-hint brand. "beta" and "dev" run the next releases; omit channel for stable. See Chrome vs Edge.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/edge/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    channel: 'beta',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Brave with Shields on

Brave sends Brave in the client-hint brands (its user agent says Chrome), adds a Sec-GPC header and randomises cores, screen and canvas between sessions. See Chrome vs Brave. Headless here (1 thread).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/brave/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox as a Windows laptop with Intel graphics

Pick the GPU the persona reports with webgl_config (vendor and renderer from Camoufox's database) and keep the rest generated: a common office laptop instead of a random desktop.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'windows',
    webgl_config: [
      'Google Inc. (Intel)',
      'ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar',
    ],
    screen: {
      minWidth: 1366,
      maxWidth: 1920,
      minHeight: 768,
      maxHeight: 1080,
    },
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Chrome over CDP (connectOverCDP or Puppeteer)

Launch with "cdp": true and connect to cdpUrl with Playwright's connectOverCDP — no client version matching — or with Puppeteer. See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    cdp: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Live view: stream the page to your code

Watch the remote browser while your code drives it: page.screencast sends a JPEG frame whenever the page changes (Playwright 1.60+). See Live view.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
// Live view: JPEG frames whenever the page changes (Playwright 1.60+)
await page.screencast.start({ quality: 60, onFrame: ({ data }) => {
  console.log(`frame: ${data.length} bytes`); // send it to your UI, a websocket, a file…
} });
await page.goto('https://example.com');
console.log(await page.title());
await page.screencast.stop();
await browser.close(); // ends the session and stops billing

cdpfleet residential: one sticky US identity

No proxy of your own: a residential US IP that stays the same for 30 minutes — and across browser sessions that reuse the session name. Tier 1 ($5/GB) from your balance; every account starts with $5 free credit. See residential proxy.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'cdpfleet-resi-country-us-session-cart1-lifetime-30',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Swap the proxy mid-session without restarting the browser

Launch as usual (every session can be swapped; "proxy_updatable" is no longer needed), then POST /admin/session/proxy with the session id and a new proxy (or proxy_rules) on the host from your wsUrl: see Swapping the proxy live for the call. Measured on prod with this launch: the first exit was in the US, the swap answered in 47 ms, and the next new connection left from Germany through cdpfleet-resi-country-de-…. The browser, its cookies and its pages stay; connections already open finish on the old proxy, so open a new page (or reload) to move (case 06 has the details).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'cdpfleet-resi-country-us-session-cart1-lifetime-30',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

cdpfleet residential with VPN Tunnel: WebRTC and HTTP/3 on the same exit

A sticky Tier 1 residential exit plus "vpn_tunnel": true: the browser's UDP leaves through the same exit as its HTTP, so WebRTC shows the exit IP instead of nothing, and HTTP/3 works. Measured on prod with this launch: the WebRTC leak test showed exactly one public address, the exit IP, and cloudflare-quic.com went h2, h2, h3, h3 over four loads (a browser switches to HTTP/3 once a page has announced it). Needs a Tier 1 or 2 token; keep it sticky, and don't combine the tunnel with proxy_rules. See VPN Tunnel and case 69.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'cdpfleet-resi-country-us-session-cart1-lifetime-30',
    headless: 'new',
    vpn_tunnel: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Community plan: the included proxy, US and sticky

On the free Community plan the proxy is included: leave proxy out, or send a cdpfleet-plan token like this one for a US exit that stays the same for 30 minutes — also for later sessions that reuse the session name. The dashboard's Proxies → Community proxy tab builds these tokens. The included proxy is shared by all Community users, so it's slower at peak times. Headless here (1 of the plan's 2 threads; headful takes both). See a first free scrape.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'cdpfleet-plan-country-us-session-shop1-lifetime-30',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with exact fingerprint values

Override single properties of the generated identity with config — here 8 CPU cores, no touch screen and a 1920×1080 screen — while Camoufox keeps everything else consistent.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'windows',
    config: {
      "navigator.hardwareConcurrency": 8,
      "navigator.maxTouchPoints": 0,
      "screen.width": 1920,
      "screen.height": 1080,
    },
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

WebKit

Render with WebKit to check layout, fonts and CSS quirks in the WebKit engine. It identifies as WebKit on Linux, so use it for rendering checks, not to impersonate another browser (TLS fingerprints).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { webkit } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/webkit/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await webkit.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Opera, developer channel

Run the next Opera before it ships: the channel is called "developer" (not "dev"). On 2026-11-08 that was Opera 140 on Chromium 155 — the user agent ends in OPR/140.0.0.0 (Edition developer) and the client hints say Opera (the developer vs stable comparison has the rest). Headless here (1 thread), so the user agent says HeadlessChrome; use headless: false where that matters.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/opera/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    channel: 'developer',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Naver Whale

Naver's browser (Whale 5.39 on Chromium 150): Whale/… in the user agent and Whale in the client hints — what Korean sites see from Naver's own browser. Headless here (1 thread); the user agent then says HeadlessChrome, so run headful for sites that check.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/whale/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Yandex Browser

Yandex's own browser (26.8 on Chromium 150): YaBrowser/26.8 in the user agent, and YaBrowser and Yowser in the client hints. Headless here (1 thread), so the user agent says HeadlessChrome; run headful for sites that check.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/yandex/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox reporting an older Firefox

ff_version: 135 makes the user agent say Firefox 135 (rv:135.0) while the engine stays Camoufox's Firefox 152 — for sites that serve a different page by version. Feature checks still see Firefox 152's APIs, so don't expect old-browser behaviour.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'windows',
    ff_version: 135,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with a macOS font list

A macOS persona that only exposes the fonts you list: with custom_fonts_only, font checks found Menlo and no longer found Arial or Courier New. Only fonts the server has can show up: Helvetica Neue and Menlo do, a family it doesn't have (Fira Code, for one) never does — see fonts and the fingerprint.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'macos',
    fonts: [
      'Helvetica Neue',
      'Menlo',
    ],
    custom_fonts_only: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with slower, human-paced cursor moves

Camoufox moves the cursor along natural paths by default; humanize: 2 lets each move take up to 2 seconds — slower clicks for sites that score how fast a visitor's pointer travels. humanize: false turns it off.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    humanize: 2,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox without WebGL

block_webgl: true turns WebGL off — no GPU fingerprint at all, and less work per page. A missing WebGL is itself a signal some sites score, so prefer a plausible GPU with webgl_config when they do (see stealth builds).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    block_webgl: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Chrome you can disconnect from and come back to

keep_alive: true (dedicated threads) keeps the browser when your code disconnects: reconnect to the same wsUrl within inactivity_timeout and your contexts, pages and logins are still there. browser.close() only disconnects — end the session with DELETE /v1/me/threads/{id} or the SDK's session.close().

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
    keep_alive: true,
    inactivity_timeout: '10m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the default context — it survives a reconnect
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // with keep_alive this only disconnects: end the session with DELETE /v1/me/threads/{sessionId}, or it ends after inactivity_timeout

Camoufox that can click inside cross-origin iframes

disable_coop: true switches off Cross-Origin-Opener-Policy so your code can click elements inside cross-origin iframes — some CAPTCHA and consent widgets live there. It is detectable, so turn it on only for the sites that need it. Reaching into frames: iframes and shadow DOM.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
    disable_coop: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Microsoft Edge over CDP (Puppeteer or connectOverCDP)

"cdp": true works on Edge as on Chrome: connect to cdpUrl with Playwright's connectOverCDP in any client version, or with Puppeteer. Pages see Edge's own brand in the client hints (see Chrome vs Edge). See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/edge/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    cdp: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Headless Chrome a person can watch and take over

Headful sessions are always watchable; a headless one needs live_view: true at launch. Then POST /admin/session/live on the session's router returns a screencast live_url (view or control) — even before your code connects — and the session is recorded. See Live view and Session recordings.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    live_view: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Chrome that stays logged in between sessions

profile: "shop-login" restores the profile's cookies, localStorage and IndexedDB at launch and saves them when the session ends — log in once and reuse it on every later launch (session cookies included). The generated code works in browser.contexts()[0], the profile itself; a newContext() would not be saved. See Persistent profiles.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    profile: 'shop-login',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the saved profile — a new context isn't saved
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox that keeps the same device and login

A Camoufox profile keeps its device: the fingerprint generated on the first launch (here a Windows one) is saved with the logins and reused, so the site sees the same computer every time. Later launches ignore fingerprint options; keep the profile on one country's exit. See Persistent profiles.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    os: 'windows',
    profile: 'store-account-1',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the saved profile — a new context isn't saved
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Firefox that stays logged in between sessions

profile works on Firefox too: cookies (session ones included) and localStorage are restored at launch and saved when the session ends. Measured on prod with this launch: a cookie and a localStorage value set in the first session were back in the second. The generated code works in browser.contexts()[0], the profile itself; a newContext() would not be saved. See Persistent profiles.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/firefox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    profile: 'shop-login',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the saved profile — a new context isn't saved
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

WebKit that stays logged in between sessions

profile works on WebKit: cookies and localStorage set in one session came back in the next (measured on prod with this launch, on example.com). One WebKit rule to know: it refuses every cookie for a domain on the Public Suffix List, such as httpbin.org, so a profile can't keep a cookie there either; localStorage still works (case 57). The generated code works in browser.contexts()[0]. See Persistent profiles.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { webkit } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/webkit/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    profile: 'shop-login',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await webkit.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the saved profile — a new context isn't saved
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Many scrapers sharing one saved login

"mode": "readonly" restores a profile without saving it back, so any number of sessions can share one login in parallel while a single normal session keeps it fresh (a second writer gets 409 profile_in_use). See Persistent profiles.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    profile: {
      id: 'shop-login',
      mode: 'readonly',
    },
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the saved profile — a new context isn't saved
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Headful Chrome that isn't recorded

Headful sessions are recorded by default within your plan's limits; "record": false opts one launch out — for pages with personal or sensitive data, or runs you never need to replay. Nothing is uploaded for that session.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
    record: false,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Brave over CDP (Puppeteer or connectOverCDP)

"cdp": true on Brave returns a cdpUrl for Puppeteer or Playwright's connectOverCDP in any version. Brave keeps its own identity over CDP too: the Brave client-hint brand, Sec-GPC and randomised fingerprint values (Chrome vs Brave). See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/brave/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    cdp: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Opera over CDP (Puppeteer or connectOverCDP)

"cdp": true on Opera returns a cdpUrl for Puppeteer or Playwright's connectOverCDP in any version. Headless over CDP, open pages in a new context (browser.newPage() does) — Opera doesn't render pages in its default context there, so keep_alive over CDP needs headful Opera. Pages see the Opera client-hint brand and OPR/ in the user agent (Opera vs Chrome). See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/opera/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    cdp: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Yandex Browser over CDP (Puppeteer or connectOverCDP)

"cdp": true works on Yandex Browser as on Chrome: connect Puppeteer or Playwright's connectOverCDP (any client version) to cdpUrl. Handy when existing Puppeteer code has to run on Yandex Browser. See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/yandex/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    cdp: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Headful Chrome on a full-HD screen

screen_size sets the virtual display of a headful session (1920×1080 here). Pages only see it with a context created with viewport: null (Python no_viewport=True) — otherwise Playwright emulates a screen the size of the viewport, 1280×720 by default. Also fills recordings and live view edge to edge.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
    screen_size: '1920x1080',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Microsoft Edge over CDP that survives a disconnect

keep_alive: true (dedicated threads) with "cdp": true: disconnect from cdpUrl, reconnect within inactivity_timeout and your pages are where you left them. Over CDP only the default context survives, so the code below opens its page in browser.contexts()[0] (a context from browser.newPage() or newContext() is gone after a reconnect). Measured on headless Edge: the default context kept its page and localStorage across a reconnect; a second context was gone. browser.close() only disconnects — end the session with DELETE /v1/me/threads/{id}. See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/edge/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    keep_alive: true,
    cdp: true,
    inactivity_timeout: '10m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the default context — it survives a reconnect
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // with keep_alive this only disconnects: end the session with DELETE /v1/me/threads/{sessionId}, or it ends after inactivity_timeout

cdpfleet residential from Windows devices, with a matching persona

-os-windows in a cdpfleet-resi token asks for a residential IP that belongs to a Windows device; Camoufox's os: "windows" makes the browser report Windows too, and geoip takes the timezone and locale from the exit. Measured: a US exit, navigator.platform Win32, the exit's timezone in the browser — the IP and the browser tell the same story. Other values: android, ios, mac (availability varies by country). Tier 1 ($5/GB) from your balance; -tier-2 to -tier-4 are cheaper tiers. See residential proxy.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'cdpfleet-resi-country-us-os-windows-session-win1-lifetime-30',
    os: 'windows',
    geoip: true,
    headless: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox without images, for less proxy traffic

block_images: true stops Camoufox from requesting images at all. Measured on books.toscrape.com's front page: 10 requests and ~152 KB instead of 30 requests and ~331 KB, loaded in ~1.6 s instead of ~7 s, with all 20 product titles still there. Read image URLs from src attributes; screenshots show no pictures. On Chromium-based browsers block images with page.route instead (bandwidth diet).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    block_images: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Open a page on the server, take over in live view, then join from code

start_url makes the server open the page itself, so no client has to connect. To click and type in it, get a control live link (POST https://<wsUrl host>/admin/session/live with { "session_id": "…", "mode": "control" }) or use Try it live in the dashboard. When your code connects, the page is already in browser.contexts()[0]: the code below waits for it instead of opening a new one. Measured on books.toscrape.com: the viewer went to the Travel category, and the code that joined afterwards found that page with its 11 books. Headless needs live_view: true on a Chromium-based browser; Firefox, Camoufox and WebKit are headful only. See Launch interactive.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    live_view: true,
    start_url: 'https://books.toscrape.com/',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the page start_url opened
const page = context.pages()[0] ?? await context.newPage();
await page.waitForURL('http*://**'); // until the start page has loaded
console.log(await page.title());
await browser.close(); // ends the session and stops billing

A hard time cap for batch jobs

overall_timeout ends the session at a fixed time after launch, busy or not. inactivity_timeout is different: it only ends a quiet session. Use the cap so a stuck loop, or a page that keeps the browser busy, can't bill past your budget. Measured with "45s": a session loading a page every few seconds was disconnected 45.3 s after launch, mid-job. Values take s, m or h. The cap doesn't wait for your job, so save results as you go.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    overall_timeout: '10m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Residential IPs for one strict host, your own proxy for the rest

Keep your own proxy for everything, and send only the host that blocks it through the cdpfleet residential proxy, from the country you need. You then pay residential prices for that host's traffic alone. Measured with ip-api.com as the rule's host: every lookup through cdpfleet-resi-country-de came from a German home ISP (Deutsche Telekom, Vodafone, 1&1, EWE), and through -country-us from US ones (Comcast, Charter, Cablevision). The same page's other hosts went out through our own proxy. Add -tier-2 to -tier-4 to the token for cheaper tiers (case 51 mixes tiers like this). The rule's name labels its traffic on your dashboard's Proxy traffic page.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    proxy_rules: [
      {
        name: 'resi-one-host',
        hosts: [
          'shop.example.com',
        ],
        proxy: 'cdpfleet-resi-country-us',
      },
    ],
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Headful Opera over CDP that survives a disconnect

keep_alive: true (dedicated threads) with "cdp": true on headful Opera: disconnect, reconnect to the same cdpUrl within inactivity_timeout, and the default context, where the code below opens its page, is where you left it. Measured: a books.toscrape.com category page and its localStorage were still there after a reconnect 8 s later. It has to be headful: over CDP only the default context survives a reconnect, and in headless Opera that context has no window (a screenshot fails with "0 width"). browser.close() only disconnects; end the session with DELETE /v1/me/threads/{id}. See CDP & Puppeteer.

// npm install [email protected]
// cdp: true — Playwright's connectOverCDP (any recent version), or Puppeteer:
//   puppeteer.connect({ browserWSEndpoint: cdpUrl, headers: { 'x-api-key': KEY } })
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/opera/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: false,
    cdp: true,
    keep_alive: true,
    inactivity_timeout: '10m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { cdpUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connectOverCDP(cdpUrl, { headers: { 'x-api-key': KEY } });
const context = browser.contexts()[0]; // the default context — it survives a reconnect
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // with keep_alive this only disconnects: end the session with DELETE /v1/me/threads/{sessionId}, or it ends after inactivity_timeout

Record a headless session and get the video

record is on by default, but a headless session is only recorded with "live_view": true or "cdp": true, as here. Measured with this launch and about 20 s of browsing: 854×480 (197 KB) on the free tier, and 1280×720 (366 KB) on a 720p recording plan. On a 1080p plan the video follows the page's viewport. With the default 1280×720 page it stayed 1280×720, while a browser.newContext({ viewport: { width: 1920, height: 1080 } }) page recorded 1920×1080 (479 KB). The video is listed seconds after the session ends: GET /v1/me/recordings, then GET /v1/me/recordings/{id}/url for a 15-minute link. See Session recording.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    live_view: true,
    record: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Camoufox with your own full fingerprint

fingerprint replaces the generated BrowserForge persona with yours; it must carry a Firefox navigator.userAgent (a Chrome or Safari one returns 400). Measured on prod with this launch: the user agent, navigator.platform (MacIntel), hardwareConcurrency (8), the screen and available sizes and the 30-bit colour depth all read back as given. Two things did not come from the object: the language follows locale (or, without it, your proxy's country via geoip; en-US with geoip: false), and devicePixelRatio stayed 1. For one or two changes on top of a generated persona, use config instead (see camoufox-config).

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/camoufox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    locale: 'en-GB',
    fingerprint: {
      navigator: {
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0',
        platform: 'MacIntel',
        hardwareConcurrency: 8,
        maxTouchPoints: 0,
      },
      screen: {
        width: 1512,
        height: 982,
        availWidth: 1512,
        availHeight: 944,
        colorDepth: 30,
        pixelDepth: 30,
      },
    },
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Keep the files a session downloads, fetch them afterwards

"downloads": true keeps every file the browser downloads in the session, so your code doesn't have to call saveAs() or stay connected while files copy. After the session, GET /v1/me/downloads?session_id=… lists each file with name, bytes, sha256 and source_url, and GET /v1/me/downloads/{id}/url returns a private link for 15 minutes. Measured on 2026-11-04: two files (200 KB and 30 bytes) started with a click and never saved by the client were listed 1.1 s after browser.close(), and the fetched bytes matched the listed sha256 and the original file. Paid plans; files are kept 2 days, up to 1 GB and 200 files. See Downloads.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: 'new',
    downloads: true,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

More memory for one heavy session

"memory": 2 (or 3, 4) gives the session that many times your plan's memory, and it counts as that many times the threads. Measured on 2026-11-05 on a shared plan: the launch answered "weight": 2, "memory_mb": 8192; 5 or "2x" get 400 invalid_memory. In case 80 a long Wikipedia article opened tab after tab ran out at 37 tabs with the default 4 GB (session_memory_limit) and reached the 50-tab cap with "memory": 2. Use it for big single-page apps, long scrolls and large PDFs or screenshots — not to go past 50 tabs. See the option memory.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    headless: 'new',
    memory: 2,
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Long-running session

Keep a session alive for up to two hours and tolerate five idle minutes. Both are capped by your plan.

// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { firefox } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/firefox/session', {
  method: 'POST',
  headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
  body: JSON.stringify({
    proxy: 'http://user:[email protected]:8080',
    headless: true,
    overall_timeout: '2h',
    inactivity_timeout: '5m',
  }),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();

// 2. Connect and drive it
const browser = await firefox.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing

Mobile device emulation

Emulate a phone client-side with Playwright's device descriptors after connecting. It works on Chromium-family browsers (Android) and WebKit (iPhone); Firefox and Camoufox don't support mobile emulation.

import { webkit, devices } from 'playwright';
// …launch /webkit/session as in the Quickstart, then:
const browser = await webkit.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const context = await browser.newContext({ ...devices['iPhone 15'] });
const page = await context.newPage();
await page.goto('https://example.com');
from playwright.sync_api import sync_playwright
# …launch /chromium/session as in the Quickstart, then:
with sync_playwright() as p:
    browser = p.chromium.connect(ws_url, headers={"x-api-key": KEY})
    context = browser.new_context(**p.devices["Pixel 7"])
    page = context.new_page()
    page.goto("https://example.com")

Handling busy and limit responses

Retry 503 with backoff — the fleet was momentarily full. Respect Retry-After on 429; retrying earlier can't succeed. Never retry 4xx other than 429 — the request itself needs fixing. See Errors & limits.

async function launch(engine, body, attempts = 5) {
  for (let i = 0; ; i++) {
    const res = await fetch(`https://starter.cdpfleet.com/${engine}/session`, {
      method: 'POST',
      headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (res.ok) return res.json();
    const retryable = res.status === 503 || res.status === 429;
    if (!retryable || i + 1 >= attempts) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
    const after = Number(res.headers.get('retry-after')) || 2 ** i;
    await new Promise((r) => setTimeout(r, Math.min(after, 60) * 1000));
  }
}