Stealth-patched Chromium

# Rebrowser

Chromium launched with Rebrowser's anti-detection patches. **Connect with Playwright 1.52** — this browser's server is pinned to 1.52 and newer clients time out on connect. The generated examples already use 1.52. `keep_alive` isn't available: the 1.52 server can't keep contexts across connections, so a launch with it returns 400. For the same reason `start_url` isn't offered: the page the server opens can't be handed to your code. `page.setContent()` never finishes on this browser (its patches replace the isolated world it runs in): use `page.goto('data:text/html,…')` or `page.evaluate(() => document.write(…))` instead.

Launch

`POST https://starter.cdpfleet.com/rebrowser/session`

Connect

`chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } })`

Playwright client

1.52.x

Versions now

**release**1.52.0

## Example

 

```node
// npm install playwright@1.52.0
// 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:pass@proxy.example.com: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
```

```python
# pip install playwright==1.52.0 requests
# Browsers run on cdpfleet, so no `playwright install` is needed.
import os
import requests
from playwright.sync_api import sync_playwright

KEY = os.environ["CDPFLEET_API_KEY"]

# 1. Launch the browser
res = requests.post(
    "https://starter.cdpfleet.com/rebrowser/session",
    headers={"x-api-key": KEY},
    json={
        "proxy": "http://user:pass@proxy.example.com:8080",
        "headless": "new",
    },
    timeout=60,
)
res.raise_for_status()
ws_url = res.json()["wsUrl"]

# 2. Connect and drive it
with sync_playwright() as p:
    browser = p.chromium.connect(ws_url, headers={"x-api-key": KEY})
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.title())
    browser.close()  # ends the session and stops billing
```

```java
// Maven: com.microsoft.playwright:playwright:1.52.0  and  com.google.code.gson:gson:2.11.0
// Run with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1: browsers run on cdpfleet.
import com.google.gson.JsonParser;
import com.microsoft.playwright.*;
import java.net.URI;
import java.net.http.*;
import java.util.Map;

public class Example {
  public static void main(String[] args) throws Exception {
    String key = System.getenv("CDPFLEET_API_KEY");

    // 1. Launch the browser
    String body = """
        {
          "proxy": "http://user:pass@proxy.example.com:8080",
          "headless": "new"
        }
        """;
    HttpResponse<String> res = HttpClient.newHttpClient().send(
        HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/rebrowser/session"))
            .header("x-api-key", key)
            .header("content-type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build(),
        HttpResponse.BodyHandlers.ofString());
    if (res.statusCode() != 200) {
      throw new RuntimeException("launch failed: " + res.statusCode() + " " + res.body());
    }
    String wsUrl = JsonParser.parseString(res.body()).getAsJsonObject().get("wsUrl").getAsString();

    // 2. Connect and drive it
    try (Playwright playwright = Playwright.create()) {
      Browser browser = playwright.chromium().connect(wsUrl,
          new BrowserType.ConnectOptions().setHeaders(Map.of("x-api-key", key)));
      Page page = browser.newPage();
      page.navigate("https://example.com");
      System.out.println(page.title());
      browser.close(); // ends the session and stops billing
    }
  }
}
```

```csharp
// dotnet add package Microsoft.Playwright --version 1.52.0
// Browsers run on cdpfleet, so no `playwright install` is needed.
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Playwright;

var key = Environment.GetEnvironmentVariable("CDPFLEET_API_KEY")!;

// 1. Launch the browser
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", key);
var body = """
    {
      "proxy": "http://user:pass@proxy.example.com:8080",
      "headless": "new"
    }
    """;
var res = await http.PostAsync("https://starter.cdpfleet.com/rebrowser/session",
    new StringContent(body, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
    throw new Exception($"launch failed: {(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
var wsUrl = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("wsUrl").GetString()!;

// 2. Connect and drive it
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectAsync(wsUrl, new BrowserTypeConnectOptions
{
    Headers = new Dictionary<string, string> { ["x-api-key"] = key },
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com");
Console.WriteLine(await page.TitleAsync());
await browser.CloseAsync(); // ends the session and stops billing
```

```go
// go get github.com/playwright-community/playwright-go@v0.5200.1
//
// One-time driver setup (needs Node.js 18+): playwright-go can no longer download the
// 1.52.0 driver itself, so build it from npm and point PLAYWRIGHT_DRIVER_PATH at it:
//   mkdir -p ~/.cache/pw-driver-1.52.0 && cd ~/.cache/pw-driver-1.52.0 &&
//     curl -sL https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz | tar xz &&
//     ln -sf "$(command -v node)" node
//   export PLAYWRIGHT_DRIVER_PATH=~/.cache/pw-driver-1.52.0
// Browsers run on cdpfleet, so no browsers are downloaded (SkipInstallBrowsers).
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/playwright-community/playwright-go"
)

func main() {
	key := os.Getenv("CDPFLEET_API_KEY")

	// 1. Launch the browser
	body := `{
	  "proxy": "http://user:pass@proxy.example.com:8080",
	  "headless": "new"
	}`
	req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/rebrowser/session", strings.NewReader(body))
	req.Header.Set("x-api-key", key)
	req.Header.Set("content-type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		msg, _ := io.ReadAll(res.Body)
		log.Fatalf("launch failed: %s %s", res.Status, msg)
	}
	var session struct {
		WsURL string `json:"wsUrl"`
	}
	if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
		log.Fatal(err)
	}

	// 2. Connect and drive it (Install fetches only the Playwright driver, once)
	opts := &playwright.RunOptions{SkipInstallBrowsers: true}
	if err := playwright.Install(opts); err != nil {
		log.Fatal(err)
	}
	pw, err := playwright.Run(opts)
	if err != nil {
		log.Fatal(err)
	}
	defer pw.Stop()
	browser, err := pw.Chromium.Connect(session.WsURL, playwright.BrowserTypeConnectOptions{
		Headers: map[string]string{"x-api-key": key},
	})
	if err != nil {
		log.Fatal(err)
	}
	page, err := browser.NewPage()
	if err != nil {
		log.Fatal(err)
	}
	if _, err := page.Goto("https://example.com"); err != nil {
		log.Fatal(err)
	}
	title, _ := page.Title()
	fmt.Println(title)
	browser.Close() // ends the session and stops billing
}
```

Want different options or a specific version? Open this browser in the [code builder](https://cdpfleet.com/docs/builder?browser=rebrowser).

## 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({...})`).

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| [`proxy`](https://cdpfleet.com/docs/options/proxy) | `string | object | array` | the 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:pass@proxy.example.com:8080"`

 |
| [`proxy_rules`](https://cdpfleet.com/docs/options/proxy_rules) | `array` | none | 

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:p@dc1.example:4444","http://u:p@dc2.example:4444"]}]`

 |
| [`inactivity_timeout`](https://cdpfleet.com/docs/options/inactivity_timeout) | `number | string` | 120s | 

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_timeout`](https://cdpfleet.com/docs/options/overall_timeout) | `number | string` | your 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"`

 |
| [`memory`](https://cdpfleet.com/docs/options/memory) | `integer` | 1 | 

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`

 |
| [`downloads`](https://cdpfleet.com/docs/options/downloads) | `boolean` | false | 

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_tunnel`](https://cdpfleet.com/docs/options/vpn_tunnel) | `boolean` | false | 

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`

 |
| [`vpn`](https://cdpfleet.com/docs/options/vpn) | `string | object` | none | 

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](https://cdpfleet.com/docs/proxies#own-network).

Example: `"home-wg"`

 |
| [`headless`](https://cdpfleet.com/docs/options/headless) | `boolean | "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"`

 |
| [`live_view`](https://cdpfleet.com/docs/options/live_view) | `boolean` | false (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](https://cdpfleet.com/docs/recordings) unless you add `"record": false`. See [Live view](https://cdpfleet.com/docs/live-view).

Example: `true`

 |
| [`screen_size`](https://cdpfleet.com/docs/options/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"`

 |
| [`record`](https://cdpfleet.com/docs/options/record) | `boolean` | true (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](https://cdpfleet.com/docs/recordings).

Example: `false`

 |

---
Source: https://cdpfleet.com/docs/browsers/rebrowser · Markdown: https://cdpfleet.com/docs/browsers/rebrowser.md · All docs for AI tools: https://cdpfleet.com/llms.txt
