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
- stable156.0.1-beta.34 pinned152.0.4 pinned150
Example
// 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,
}),
});
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
# 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:[email protected]: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
// 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:[email protected]: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
}
}
}
// 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:[email protected]: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 get github.com/playwright-community/[email protected]
//
// 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:[email protected]: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.
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 | string | object | array | the free Community proxy | A proxy URL ( 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 Optional: without a proxy (and without Example: "http://user:[email protected]:8080" |
proxy_rules | array | none | A list of 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 Example: [{"name":"dc-assets","hosts":["*.cloudfront.net","static.example.com"],"proxy":["http://u:[email protected]:4444","http://u:[email protected]:4444"]}] |
inactivity_timeout | number | string | 120s | The timer resets on any protocol traffic in either direction. A number of seconds ( 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 | number | string | your plan's maximum session length (24 h) | A number of seconds ( 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 | 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 Use it for: Memory-heavy work that would otherwise end with Only Example: 2 |
downloads | boolean | false | With 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 ( Paid plans ( Example: true |
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 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 Example: true |
vpn | string | object | none | The name of a VPN saved on the dashboard's Proxies → My VPNs page ( 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 ( Example: "home-wg" |
headless | boolean | "virtual" | false (headful, on a virtual display) | Any truthy value runs headless — including the strings Use it for: Bulk Camoufox jobs where nobody watches; Example: false |
version | string | none (latest stable) | Request a retained previous major, e.g. 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 Example: "152" |
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 Example: "windows" |
locale | string | array | derived from the proxy's exit IP | The first locale drives Use it for: Localisation checks and geo work: the site sees the language you choose in An unknown locale fails the launch. Example: "en-US" |
geoip | boolean | true | At launch the browser looks up its public IP through your proxy and aligns its identity with that location. Set 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 | boolean | number | true | Moves the cursor along natural paths. A number sets the maximum movement duration in seconds. Set 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 Example: 1.5 |
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 | boolean | false | When left enabled (with Use it for: Sessions behind a proxy that must not show any WebRTC address at all. With Example: true |
block_webgl | boolean | false | Turns WebGL off. Overrides Use it for: Pages where any WebGL fingerprint is a risk and graphics aren't needed. A missing WebGL is itself unusual, so prefer A missing WebGL is itself a fingerprint signal. Example: false |
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 | object | none | Bounds for the generated screen: Use it for: Keeping generated personas within a screen range you expect, for example common laptop sizes, without writing a whole fingerprint. Ignored when Example: {"minWidth":1280,"maxWidth":1920,"minHeight":720,"maxHeight":1080} |
window | [width, height] | random | Use this window size instead of a random one, e.g. Use it for: Layout-sensitive scraping and screenshots that need the same window size on every launch. Ignored when Example: [1280,720] |
fingerprint | object | generated | Supply a full Firefox fingerprint object instead of a generated one. It must include 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 | object | none | A map of Camoufox fingerprint properties to values, e.g. 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 Example: {"navigator.hardwareConcurrency":8,"navigator.maxTouchPoints":0} |
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 | boolean | false | Drop the target OS's default font list and expose only the families listed in Use it for: Tests of how a site reacts to a minimal or specific font list; rarely what a normal persona needs. Requires Example: true |
webgl_config | [vendor, renderer] | generated | Pick a WebGL identity from Camoufox's database, e.g. with 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 Example: ["Google Inc. (Intel)","ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar"] |
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 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 | boolean | false | Enables Use it for: Reading values exactly as the page's own scripts see them, and calling functions the page defines, from Main-world scripts are visible to the page. Example: true |
screencast | boolean | false | Not a launch option: the generated program calls 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 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. 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 Example: "1366x768" |
keep_alive | boolean | false | A disconnect no longer ends the session: reconnect to the same 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 ( Example: true |
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 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 ( Example: "https://example.com" |
profile | string | object | none | A name ( 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 Example: "shop-login" |
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 Use it for: Keeping a video of each run for debugging, QA evidence or audits. Set Recording applies to headful sessions and to headless ones launched with Example: false |