> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Configure a Stealth Browser

> Launch a geo-pinned, fingerprint-consistent browser and verify what websites actually see.

Stealth is mostly about not contradicting yourself. BCTRL runs Kameleo underneath, which picks a real, internally coherent fingerprint - your job in the config is to keep the parts you control (proxy geo, locale, WebRTC) telling the same story.

```ts TypeScript
import { Bctrl } from "@bctrl/sdk";
import { chromium } from "playwright";

const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });

const runtime = await bctrl.runtimes.create({
type: "browser",
name: "stealth-recipe",
config: {
stealth: "ultra",
proxy: {
type: "managed-rotating",
country: "US",
rotation: "sticky", // one exit IP for the whole session
stickyKey: "stealth-recipe",
preference: "quality",
},
fingerprint: {
device: "desktop",
os: "windows",
browser: "chrome",
locale: ["en-US", "en"], // agree with the proxy country
},
webRtcProxyOnly: true, // WebRTC can't leak your real IP around the proxy
},
});
const { connectUrl } = await bctrl.runtimes.start(runtime.id);

// See what websites see.
const browser = await chromium.connectOverCDP(connectUrl);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = context.pages()[0] ?? (await context.newPage());

await page.goto("https://pixelscan.net");
await page.waitForTimeout(15_000); // let the checks finish
await page.screenshot({ path: "pixelscan.png", fullPage: true });

await browser.close();
await bctrl.runtimes.stop(runtime.id);

```

```python Python
from bctrl import Bctrl
from playwright.sync_api import sync_playwright

bctrl = Bctrl()

config = {
    "stealth": "ultra",
    "proxy": {
        "type": "managed-rotating",
        "country": "US",
        "rotation": "sticky",        # one exit IP for the whole session
        "stickyKey": "stealth-recipe",
        "preference": "quality",
    },
    "fingerprint": {
        "device": "desktop",
        "os": "windows",
        "browser": "chrome",
        "locale": ["en-US", "en"],   # agree with the proxy country
    },
    "webRtcProxyOnly": True,         # WebRTC can't leak your real IP around the proxy
}

with bctrl.runtimes.started_browser(name="stealth-recipe", config=config) as rt:
    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp(rt.connect_url)
        context = browser.contexts[0]
        page = context.pages[0] if context.pages else context.new_page()

        # See what websites see.
        page.goto("https://pixelscan.net")
        page.wait_for_timeout(15_000)  # let the checks finish
        page.screenshot(path="pixelscan.png", full_page=True)

        browser.close()
```

> 📸 **Content TODO:** run this once and embed the resulting pixelscan screenshot (a clean "consistent" result). A CreepJS capture works too. Date-stamp it in the caption - detection checkers evolve and readers will re-test.

## Why these knobs

* **`stealth: "ultra"`** - the anti-detection hardening level. `medium`/`high`/`ultra` trade resource use for depth; start with `high` and move up if a target still challenges you.
* **Sticky rotating proxy** - rotation between sessions, one exit IP within a session. An IP that changes mid-session is itself a signal.
* **`fingerprint` filters, not values** - BCTRL defaults to a desktop Windows search and randomly selects Chrome, Edge, or Safari when `browser` is omitted. You can narrow Kameleo's search further (`desktop` + `windows` + `chrome`), and it picks a real fingerprint where canvas, WebGL, fonts, and UA already agree. Never hand-build the parts yourself.
* **`locale` matches the proxy country** - a US exit IP with a `de-DE` language stack is a contradiction detectors look for.
* **`webRtcProxyOnly`** - WebRTC is the classic side channel that reveals the real IP behind a proxy; force it through the proxy.

## Keep the identity across sessions

A fingerprint that's brand new on every visit is also a signal. For flows that return to the same site, use a [profile-backed runtime](/sdk/runtime-config#profile-backed-runtimes) (`config.profile: true`): cookies, logins, and the fingerprint identity persist across starts, so you come back as the same browser - not a fresh stranger.

## Next

* [Runtime configuration](/sdk/runtime-config) - every config option
* [Proxies](/sdk/proxies) - pools, static IPs, and bring-your-own
* [Solve CAPTCHAs](/cookbook/solve-captchas) - for the challenges that still get through