> 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.

# Connect with CDP

> Attach Playwright, Puppeteer, or another CDP client to a running browser Runtime.

The CDP connection is a **Run-scoped browser connection**. Starting a browser
Runtime opens a Run and returns a credentialed `cdpUrl`. Use that URL to
drive the browser yourself while BCTRL records the same Run for observability.

The URL is valid only while its Run is active. Treat it as a credential: do not
log it, persist it, or expose it to page code.

## Get the connection URL

An ephemeral Runtime starts during creation, so its connection is returned on
the create response:

```ts
const runtime = await bctrl.runtimes.create({
  name: "checkout-job",
});

if (!runtime.connection) {
  throw new Error("Runtime did not start");
}

const { cdpUrl, runId } = runtime.connection;
```

For a reusable profile-backed Runtime, create it stopped and start it when you
need a new session:

```ts
const runtime = await bctrl.runtimes.create({
  name: "customer-portal",
  profile: true,
});

const started = await bctrl.runtimes.start(runtime.id);
const { connection, runId } = started;
const { cdpUrl } = connection;
```

The start response contains:

| Field                     | Type                   | Always present | Meaning                                      |
| ------------------------- | ---------------------- | -------------- | -------------------------------------------- |
| `runtimeId`               | `string`               | Yes            | Runtime that owns the browser.               |
| `runId`                   | `string`               | Yes            | Run for this browser session.                |
| `connection.cdpUrl`       | `string`               | Yes            | Run-scoped CDP endpoint.                     |
| `connection.webDriverUrl` | `string`               | Yes            | WebDriver endpoint for the same browser Run. |
| `started`                 | `boolean`              | Yes            | Whether this call opened a new Run.          |
| `connection.recording`    | `{ enabled: boolean }` | Yes            | Whether recording is enabled.                |

Calling `start()` for an already-active Runtime reuses its active Run and
returns the same connection URL. When the Run ends, the URL stops working. A
later Run receives a new URL.

## Playwright

```ts
import { chromium } from "playwright";

const browser = await chromium.connectOverCDP(cdpUrl);
const context = browser.contexts()[0];
const page = context.pages()[0] ?? (await context.newPage());

await page.goto("https://example.com");
console.log(await page.title());

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

The Runtime owns the browser lifecycle. Close the client connection when your
work is complete, then stop the Runtime.

## Puppeteer

```ts
import puppeteer from "puppeteer-core";

const browser = await puppeteer.connect({
  browserWSEndpoint: cdpUrl,
});

const pages = await browser.pages();
const page = pages[0] ?? (await browser.newPage());

await page.goto("https://example.com");
console.log(await page.title());

await browser.disconnect();
await bctrl.runtimes.stop(runtime.id);
```

Use `disconnect()` with Puppeteer because the Runtime, not Puppeteer, owns the
browser process.

## Connection ownership

Only one external CDP controller can be connected to a Run at a time. Close the
current CDP connection before connecting another client.

BCTRL's live Views, recordings, traces, and events do not consume the external
CDP slot. Hosted Tools and Agent turns use the same browser, so coordinate
page-level actions when an external client and hosted automation are active at
the same time.

## Stop the session

Stopping the Runtime ends its active Run and invalidates the connection URL:

```ts
const stopped = await bctrl.runtimes.stop(runtime.id);
console.log(stopped.runId, stopped.status, stopped.stopped);
```

Use the `runId` with [Runs](/sdk/runs) to inspect trace spans, raw events,
recordings, usage, and files produced by the session.

## Next

* [Connect with WebDriver](/sdk/connect-webdriver) — attach Selenium
* [Runtimes](/sdk/runtimes) — configure browser identity and launch options
* [Runs](/sdk/runs) — inspect what happened during the session