Based on Firefox source; not affiliated with Mozilla

# Camoufox

Camoufox is an anti-detect browser based on Firefox source (not affiliated with Mozilla) that spoofs a complete, consistent fingerprint at the engine level. Set its identity with launch options (below) rather than with client-side `newContext` settings, which would fight the injected identity. Bring a `proxy` for real work (without one the free Community proxy is used), and by default the identity is matched to the proxy's location. `POST /session` is an alias for this browser.

Launch

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

Connect

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

Playwright client

1.60.x

Versions now

**stable**156.0.1-beta.34 **pinned**152.0.4 **pinned**150

## Example

 

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

```python
# pip install playwright==1.60.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/camoufox/session",
    headers={"x-api-key": KEY},
    json={
        "proxy": "http://user:pass@proxy.example.com:8080",
        "headless": False,
    },
    timeout=60,
)
res.raise_for_status()
ws_url = res.json()["wsUrl"]

# 2. Connect and drive it
with sync_playwright() as p:
    browser = p.firefox.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.60.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": false
        }
        """;
    HttpResponse<String> res = HttpClient.newHttpClient().send(
        HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/camoufox/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.firefox().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.60.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": false
    }
    """;
var res = await http.PostAsync("https://starter.cdpfleet.com/camoufox/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.Firefox.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.6000.0
//
// One-time driver setup (needs Node.js 18+): playwright-go can no longer download the
// 1.60.0 driver itself, so build it from npm and point PLAYWRIGHT_DRIVER_PATH at it:
//   mkdir -p ~/.cache/pw-driver-1.60.0 && cd ~/.cache/pw-driver-1.60.0 &&
//     curl -sL https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz | tar xz &&
//     ln -sf "$(command -v node)" node
//   export PLAYWRIGHT_DRIVER_PATH=~/.cache/pw-driver-1.60.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": false
	}`
	req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/camoufox/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.Firefox.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=camoufox).

## 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({...})`) — but for Camoufox prefer the fingerprint options below.

| 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 | "virtual"` | false (headful, on a virtual display) | 

Any truthy value runs headless — including the strings `"false"` and `"0"`, so send a real boolean. `false`, `"virtual"` or omitting it runs headful on a virtual display, which Camoufox recommends for stealth (billed as 2 threads; headless is 1).

**Use it for:** Bulk Camoufox jobs where nobody watches; `"virtual"` keeps headful behaviour on a virtual display for sites that notice true headless.

Example: `false`

 |
| [`version`](https://cdpfleet.com/docs/options/version) | `string` | none (latest stable) | 

Request a retained previous major, e.g. `"152"`. The majors available right now are listed on the browser's page. A major no server in the fleet has returns `400 {"error": "invalid_version"}` with the majors you can pin in `message`, rather than silently running a different version; one that exists but has no free server right now returns `503 version_unavailable`.

**Use it for:** Reproducing a bug on an older browser, keeping a scraper on a known-good build while you test the new one, or matching a customer's browser.

Previous majors rotate out automatically: Chrome keeps its two most recent previous majors and drops the oldest within about a day of a new stable release; Camoufox keeps recent majors plus long-term ones. Read the live list rather than hard-coding a number. Send either `version` or a pre-release `channel`, not both: together they return `400` (`channel: "stable"` with a `version` is fine).

Example: `"152"`

 |
| [`os`](https://cdpfleet.com/docs/options/os) | `"windows" | "macos" | "linux" | array` | random among windows / macos / linux | 

The OS the spoofed fingerprint claims to run on. A list picks one at random per session.

**Use it for:** Matching the persona to who a site expects (a Windows desktop for most consumer sites, macOS for design or creative tools), and keeping one account on the same OS every time.

Required when `webgl_config` is set. Any other value returns 400.

Example: `"windows"`

 |
| [`locale`](https://cdpfleet.com/docs/options/locale) | `string | array` | derived from the proxy's exit IP | 

The first locale drives `navigator.language` and the Intl API, e.g. `"en-US"`, or a list / comma-separated string of several. Leave it unset to match the proxy's country automatically (with `geoip`).

**Use it for:** Localisation checks and geo work: the site sees the language you choose in `navigator.languages`, Accept-Language and number and date formats. Leave it unset to follow the proxy's country.

An unknown locale fails the launch.

Example: `"en-US"`

 |
| [`geoip`](https://cdpfleet.com/docs/options/geoip) | `boolean` | true | 

At launch the browser looks up its public IP through your proxy and aligns its identity with that location. Set `false` to skip the lookup (faster start, but the location won't match the IP).

**Use it for:** Any session behind a proxy in another country: timezone, locale and geolocation follow the exit IP, so the persona doesn't contradict its own address.

If the proxy can't reach the IP lookup services, the launch fails.

Example: `false`

 |
| [`humanize`](https://cdpfleet.com/docs/options/humanize) | `boolean | number` | true | 

Moves the cursor along natural paths. A number sets the maximum movement duration in seconds. Set `false` to disable.

**Use it for:** Pages that score mouse behaviour (forms, checkouts, sign-ups). Turn it off for bulk scraping, where instant moves are faster.

If you pin an older `version` (150 or 152), a humanized mouse move occasionally never returns: a Camoufox bug fixed in the current build. Use the default version, give humanized moves and clicks a timeout, or set `false`.

Example: `1.5`

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

Saves bandwidth and time on image-heavy pages.

**Use it for:** Bulk text scraping where images are dead weight: fewer requests, less traffic (and less residential spend), faster pages. Measured in the camoufox-block-images recipe: 30 requests down to 10 on a catalogue page.

Blocked images are themselves a detectable signal.

Example: `true`

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

When left enabled (with `geoip` on), WebRTC reports the proxy's exit IP instead of the real one.

**Use it for:** Sessions behind a proxy that must not show any WebRTC address at all. With `vpn_tunnel` you get the exit IP instead, which looks more like a home connection.

Example: `true`

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

Turns WebGL off. Overrides `webgl_config`.

**Use it for:** Pages where any WebGL fingerprint is a risk and graphics aren't needed. A missing WebGL is itself unusual, so prefer `webgl_config` when the site expects a GPU.

A missing WebGL is itself a fingerprint signal.

Example: `false`

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

Lets you click elements inside cross-origin iframes, such as some CAPTCHA widgets.

**Use it for:** Scrapers and agents that must click inside a cross-origin iframe — a CAPTCHA widget, an embedded login or payment frame — on Camoufox. Leave it off otherwise: a site can detect it.

Detectable.

Example: `true`

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

Bounds for the generated screen: `{ "minWidth", "maxWidth", "minHeight", "maxHeight" }` in pixels.

**Use it for:** Keeping generated personas within a screen range you expect, for example common laptop sizes, without writing a whole fingerprint.

Ignored when `fingerprint` is given.

Example: `{"minWidth":1280,"maxWidth":1920,"minHeight":720,"maxHeight":1080}`

 |
| [`window`](https://cdpfleet.com/docs/options/window) | `[width, height]` | random | 

Use this window size instead of a random one, e.g. `[1280, 720]`.

**Use it for:** Layout-sensitive scraping and screenshots that need the same window size on every launch.

Ignored when `fingerprint` is given.

Example: `[1280,720]`

 |
| [`fingerprint`](https://cdpfleet.com/docs/options/fingerprint) | `object` | generated | 

Supply a full Firefox fingerprint object instead of a generated one. It must include `navigator.userAgent` for Firefox. For targeted changes, prefer `config`.

**Use it for:** Replaying one exact device you already have, such as a persona captured from a real browser, instead of a generated one.

A non-Firefox fingerprint returns 400.

Example: `{"navigator":{"userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0"},"screen":{}}`

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

A map of Camoufox fingerprint properties to values, e.g. `navigator.userAgent`, `navigator.hardwareConcurrency`, `navigator.maxTouchPoints`, `screen.width`, `timezone`, `webGl:vendor`. Your values win over generated ones. Use this — not client-side `newContext` options — to change the user agent.

**Use it for:** Changing one or two properties of a generated persona, such as the core count, while Camoufox keeps everything else consistent.

Strictly validated: an unknown property (e.g. Chromium-only `navigator.deviceMemory`) or a wrong value type returns 400. Keep overrides consistent with `os` (for example, touch points stay 0 on a desktop OS).

Example: `{"navigator.hardwareConcurrency":8,"navigator.maxTouchPoints":0}`

 |
| [`fonts`](https://cdpfleet.com/docs/options/fonts) | `array` | the target OS's fonts | 

Font family names to expose in addition to the OS defaults.

**Use it for:** Personas for audiences whose machines have extra fonts (design tools, office suites, CJK fonts), so the font list looks lived-in.

Example: `["Arial","Helvetica"]`

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

Drop the target OS's default font list and expose only the families listed in `fonts`.

**Use it for:** Tests of how a site reacts to a minimal or specific font list; rarely what a normal persona needs.

Requires `fonts`. A listed family only renders if the server can draw it: Camoufox's bundled fonts are switched off in this mode, so a family that exists only in that bundle (Consolas, for example) stays invisible.

Example: `true`

 |
| [`webgl_config`](https://cdpfleet.com/docs/options/webgl_config) | `[vendor, renderer]` | generated | 

Pick a WebGL identity from Camoufox's database, e.g. with `"os": "windows"`: `["Google Inc. (Intel)", "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar"]`.

**Use it for:** Matching the GPU to the persona when a site checks the WebGL renderer, for example an Intel laptop GPU for a Windows laptop persona.

Requires `os`, and the pair must be valid for that OS: otherwise the launch returns 400 with the valid pairs. Ignored with `block_webgl`.

Example: `["Google Inc. (Intel)","ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar"]`

 |
| [`ff_version`](https://cdpfleet.com/docs/options/ff_version) | `number` | the real Firefox major | 

Changes the version the fingerprint reports (user agent etc.), not the actual browser build. To run an older build, use `version`.

**Use it for:** Sites that only support certain Firefox versions, or reproducing what an older Firefox user sees.

A reported version that differs from the real engine is detectable.

Example: `135`

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

Enables `page.evaluate("mw:" + script)` to run in the page's own JavaScript world instead of an isolated one.

**Use it for:** Reading values exactly as the page's own scripts see them, and calling functions the page defines, from `page.evaluate`.

Main-world scripts are visible to the page.

Example: `true`

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

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](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"`

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

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_url`](https://cdpfleet.com/docs/options/start_url) | `string (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](https://cdpfleet.com/docs/live-view#launch-interactive).

Example: `"https://example.com"`

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

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

 |
| [`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/camoufox · Markdown: https://cdpfleet.com/docs/browsers/camoufox.md · All docs for AI tools: https://cdpfleet.com/llms.txt
