cdpfleet Docs Sign up free

Stealth-patched Chromium

Patchright

Chromium launched with Patchright's anti-detection patches (leaner automation flags, no Runtime.enable leak). The patches apply on our side, so connect with ordinary Playwright.

Launch
POST https://starter.cdpfleet.com/patchright/session
Connect
chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } })
Playwright client
1.60.x
Versions now
release1.60.2

Example

// 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: '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

Want different options or a specific version? Open this browser in the code builder.

Launch options

Send these as the JSON body of the launch request. Everything is optional; unknown fields are ignored. Context options such as viewport, user agent or cookies are set client-side after connecting (browser.newContext({...})).

OptionTypeDefaultWhat it does
proxystring | object | arraythe free Community proxy

A proxy URL (http://user:pass@host:port, https://…, socks5://user:pass@host:port; no scheme means http://), an object { "server": "http://host:port", "username": "…", "password": "…" }, or a list of proxies used round-robin as a pool. SOCKS5 with authentication works on every browser.

Use it for: Every session that needs a particular exit: your own proxy for the IP and country you choose, a list as a pool to spread requests across IPs, or a cdpfleet-resi token for residential IPs billed per GB.

Optional: without a proxy (and without vpn) the free Community proxy is used — shared, slower and capped (503 community_capacity_full when busy), so bring your own or use the residential proxy for work that matters. Sessions never browse from our servers' own IPs. An unparseable proxy returns 400.

Example: "http://user:[email protected]:8080"
proxy_rulesarraynone

A list of { "name": "…", "hosts": [...], "proxy": "…" | [...] } rules. Requests to a host matching a rule use that rule's proxy (round-robin when it is a list); every other host uses proxy. Host patterns support * wildcards, e.g. *.cdn.example.com or static.*. The optional name (up to 64 characters) labels the rule's traffic on your dashboard's Proxy traffic page and in its CSV export.

Use it for: Scrapers that want an expensive exit only where it matters: residential for the target site, a cheaper proxy for images, CDNs and APIs, or a different country per host.

Needs proxy to be set as the default route. Rules route through a proxy only; routing a host with no proxy (direct from our servers) is not available. The first matching rule wins — keep rules from overlapping so traffic is attributed to the right one.

Example: [{"name":"dc-assets","hosts":["*.cloudfront.net","static.example.com"],"proxy":["http://u:[email protected]:4444","http://u:[email protected]:4444"]}]
inactivity_timeoutnumber | string120s

The timer resets on any protocol traffic in either direction. A number of seconds (90, 1.5) or a string with a unit: 500ms, 45s, 10m, 2h (digits only, no spaces or decimals). Clamped to 1 s – 24 h. An unparseable value silently falls back to the default.

Use it for: Freeing the thread quickly when a script dies (short), or giving a person or a slow step time to think (long), up to your plan's cap.

Up to 10 minutes on shared threads, 5 on Community and 24 h on dedicated; a longer value is cut to your plan's maximum. Idle minutes are billed like any others. A slow page load through a proxy can produce no traffic for a while, so keep it at 60 s or more. Never longer than the session's overall timeout.

Example: "2m"
overall_timeoutnumber | stringyour plan's maximum session length (24 h)

A number of seconds (90, 1.5) or a string with a unit: 500ms, 45s, 10m, 2h (digits only, no spaces or decimals). Clamped to 1 s – 24 h. An unparseable value silently falls back to the default. cdpfleet also caps it at your plan's maximum session length (24 h) and, for shared threads, at your remaining daily minutes.

Use it for: A hard time budget per job, so a stuck loop can't hold a thread for hours: batch scrapers and agents with a deadline.

Example: "30m"
memoryinteger1

A multiplier of your plan's per-session memory (Community 2 GB, shared 4 GB, dedicated 6 GB; headful sessions get 512 MB more for the display). The session's weight is multiplied too: headless with "memory": 2 takes 2 threads, headful 4, and its minutes are used up as fast. The launch response returns the effective weight and memory_mb.

Use it for: Memory-heavy work that would otherwise end with session_memory_limit: many tabs at once, big single-page apps, long infinite scrolls, or large PDFs and screenshots.

Only 1, 2, 3 or 4 as a JSON number; anything else (e.g. 4096 or "2x") is refused with 400 invalid_memory. A launch that would go over your threads gets 429 threads_exceeded as usual, so a Community headful session can't use it. The tab cap (50) stays the same.

Example: 2
downloadsbooleanfalse

With true, every file the browser downloads in this session is kept: after the session ends it appears under GET /v1/me/downloads and on the dashboard's Sessions page, for 2 days. Your own code still receives downloads as usual (download.saveAs() in Playwright).

Use it for: Downloads you can't catch in code: a person clicking in live view or Launch interactive, an AI agent working through the MCP server, a script that crashed or disconnected (keep_alive), or reports and exports you want to collect later.

Paid plans (403 downloads_not_available on Community). Up to 1 GB and 200 files per session, and 1 GB per account (the oldest files go first); kept 2 days. Not kept: downloads in contexts a raw-CDP client creates itself, and contexts opened with acceptDownloads: false. WebKit doesn't treat a text/plain response with Content-Disposition: attachment as a download.

Example: true
vpn_tunnelbooleanfalse

The browser gets a network of its own whose only way out is the session's residential proxy, so UDP works as on a home connection: WebRTC shows the residential exit IP as its public address, and HTTP/3 (QUIC) works. Without it, a proxied session sends no UDP at all — WebRTC shows only an anonymous mDNS name and sites fall back to HTTP/2. The TLS fingerprint and the exit IP are the same either way. It is not a VPN: the residential proxy still opens the connections to the site, so the TCP/IP fingerprint the site sees is the exit device's, as without the tunnel. UDP traffic is billed at the token's tier and listed as host (udp) on the Proxy traffic page.

Use it for: Work that needs WebRTC or HTTP/3 to look like a home connection, such as video calls or sites that compare the WebRTC address with the page's IP, on a sticky residential exit.

Needs the default proxy to be a cdpfleet-resi token on Tier 1 or Tier 2 (no -tier-N is Tier 1), or, on dedicated plans, your own proxy too (UDP then works if your SOCKS5 proxy relays it; not billed); anything else — Tier 3/4, a pool, your own proxy on other plans — returns 400 tunnel_not_available. proxy_rules still route page traffic by host, but UDP (WebRTC, HTTP/3) can leave through a rule's proxy instead of the default, because a UDP flow carries no host for the rules to match: for WebRTC that matches the page's exit, don't combine the tunnel with proxy_rules. A live swap must keep a Tier 1/2 token. Sticky sessions (-session-…) work best: with a rotating exit, WebRTC and HTTP/3 may show a different residential IP than the page. Adds about 0.1 s to the launch.

Example: true
vpnstring | objectnone

The name of a VPN saved on the dashboard's Proxies → My VPNs page ("vpn": "home-wg"; the server looks the config up at launch, so no secret goes into your code), or the config itself: { "type": "wireguard" | "amneziawg" | "singbox" | "openvpn", "config": "<text>", "username": "…", "password": "…" }. config is a wg-quick .conf (WireGuard, AmneziaWG incl. Jc/Jmin/Jmax/S1–S4/H1–H4/I1–I5), a sing-box share link (vless:// incl. Reality, vmess://, trojan://, ss://, hysteria2:///hy2://, tuic://) or outbound JSON (also SSH), or an .ovpn file (username/password for OpenVPN auth). The launch returns once a real HTTPS request through the VPN has worked. All traffic, UDP included, uses the VPN; DNS only through it; IPv6 off; if the VPN drops, the session goes offline — our servers' IP is never used.

Use it for: Dedicated-plan users who already run their own VPN (WireGuard, AmneziaWG, OpenVPN, sing-box): the whole browser, UDP included, leaves through it, so pages and WebRTC show the VPN's address.

Dedicated plans only (403 vpn_requires_dedicated). Not with proxy, proxy_rules or a live swap (400 vpn_not_available). Config, username and password together up to 64 KB (400 invalid_vpn_config). A server that doesn't answer or refuses the credentials returns 400 vpn_connect_failed. Not billed. Keep the config out of your code: read it from a file or an environment variable. See Own network.

Example: "home-wg"
headlessboolean | "new" | "shell"false (headful, on a virtual display)

true or "new" runs Chrome's new headless mode; "shell" runs the lightweight headless shell. Anything else — including 1 and "false" — runs headful on a virtual display, which is harder to detect and is billed as 2 threads (headless is 1).

Use it for: Bulk scraping and tests where nobody watches: lower memory and faster launches. Leave it headful (the default) when a site checks for headless tells, or when you want a recording or live view without extra options.

Example: "new"
screencastbooleanfalse

Not a launch option: the generated program calls page.screencast.start({ onFrame }), which sends JPEG frames of the page to your code over the session's own connection whenever it changes — forward them to your UI, a websocket or a file. Needs a Playwright 1.60+ client.

Use it for: Watching or recording a headless page from your own code as a stream of JPEG frames, for debugging dashboards or human review, without a full live view.

View-only and only for the client that drives the page. With "cdp": true, use CDP Page.startScreencast instead. To watch from somewhere else — or take control — see live_view and Live view.

Example: true
live_viewbooleanfalse (headful sessions are always watchable)

Headful sessions can always be watched or taken over (POST /admin/session/live → a VNC-over-WebSocket live_url, or the dashboard's Watch / Take control). Headless Chromium-based sessions need "live_view": true at launch; they are then streamed as JPEG frames ("protocol": "screencast").

Use it for: A person watching or taking over the session in the browser (a login, a 2FA step, a check that needs a human), or recording a headless session.

Headless Chromium-based browsers only (Camoufox, Firefox and WebKit are watchable when headful, their default). Doesn't give your code CDP access — add "cdp": true for that. The session is also recorded unless you add "record": false. See Live view.

Example: true
screen_size"WxH" | { width, height }the server's display (1280×720 window)

Sets the screen the browser runs on, from 640×480 to 3840×2160, e.g. "1366x768" or { "width": 1920, "height": 1080 }. Pages see it as screen.width/height; Chromium windows open at that size (Playwright then resizes each context's window to its viewport — use viewport: null to keep the full display, in live view and recordings too); Camoufox pins its fingerprint screen to it unless you pass screen.

Use it for: Responsive and visual tests at a given resolution, and personas that need a common desktop or laptop screen instead of the server default.

Headful sessions only — a headless launch with screen_size returns 400. Playwright contexts report a screen equal to their viewport (1280×720) unless you open them with viewport: null (Python no_viewport=True, Java setViewportSize(null), C# ViewportSize = ViewportSize.NoViewport, Go NoViewport: playwright.Bool(true)); Camoufox always reports the display — the nearest real-world screen size no larger than it (exact for common sizes such as 1366×768 or 1920×1080); with "os": "macos" use 1440×900 or larger, since no Mac screen is smaller (400 otherwise).

Example: "1366x768"
keep_alivebooleanfalse

A disconnect no longer ends the session: reconnect to the same wsUrl or cdpUrl until inactivity_timeout runs out. Over wsUrl: on the first connect create your context and page as usual (browser.newContext(), newPage()); after a reconnect, browser.contexts() lists the contexts you created, with their pages, cookies and page state intact. Over cdpUrl (connectOverCDP): work in the browser's default context, browser.contexts()[0] — contexts created with newContext() over CDP close when the client disconnects.

Use it for: Agents and step-by-step workflows that disconnect between steps, and code that may lose its connection, without losing the logged-in browser.

Not on Rebrowser. Dedicated threads only (403 keep_alive_requires_dedicated on shared plans). The session is billed while it's alive, attached or not — and browser.close() only disconnects: the session ends after inactivity_timeout without a client, or at once with DELETE /v1/me/threads/{sessionId} (or End in the dashboard). Set inactivity_timeout to how long it may wait for you.

Example: true
start_urlstring (http/https URL)none

The server opens the page itself and the session doesn't wait for your code to connect: open a live link in control mode (POST /admin/session/live) and use it like a local browser. Mouse and keyboard input in control mode count as activity, so the session ends after inactivity_timeout without input. Your code can still connect later — browser.contexts()[0].pages() holds the page. This is what the dashboard's Launch interactive does.

Use it for: A person logs in or passes a check by hand in live view first; then your code connects and carries on in the same page.

http or https, at most 2,048 characters (400 invalid_start_url). Headless needs "live_view": true on a Chromium-based browser; Firefox, Camoufox and WebKit only headful (400 start_url_not_viewable). Without keep_alive, a client that connects and then disconnects ends the session. See Launch interactive.

Example: "https://example.com"
profilestring | objectnone

A name ("shop-login", up to 64 letters, digits, -, _) or { "id": "shop-login", "mode": "readonly" }. The first launch with a new name creates the profile; later launches restore its cookies, localStorage and IndexedDB, and the profile is saved when the session ends. Session cookies are kept too; sessionStorage (per tab) is not. Work in browser.contexts()[0] — a context made with newContext() is not saved. "mode": "readonly" restores without saving and can run in parallel; a second normal session gets 409 profile_in_use. On Camoufox the device fingerprint is kept with the profile. See Persistent profiles.

Use it for: Staying logged in between sessions: log in once, then every later launch starts with the same cookies and storage (and, on Camoufox, the same device).

Every browser except Whale and Rebrowser. Keeps cookies (session cookies too), localStorage and IndexedDB; not sessionStorage. 20 profiles per shared thread and 30 per dedicated thread (at least 100), 50 MB each; list and delete with /v1/me/profiles.

Example: "shop-login"
recordbooleantrue (when recording applies)

Sessions are recorded by default within your recording limits: headful sessions from their display, headless sessions only when launched with "cdp": true or "live_view": true. "record": false turns it off for one launch.

Use it for: Keeping a video of each run for debugging, QA evidence or audits. Set false when you don't need it, to keep the recording quota for runs that matter.

Recording applies to headful sessions and to headless ones launched with "cdp": true or "live_view": true, at 480p (720p or 1080p on a recording plan). Recordings appear on the dashboard's Sessions page. See Session recording.

Example: false