# Quickstart

> Create a browser key, start a Chrome browser at browsers.openwaf.dev and drive it with curl, Playwright, Puppeteer or the Playwright MCP server.

This guide creates a browser key, starts a browser, drives it from your code and stops it. It takes about five minutes.

## Before you start

- **Access.** OpenWAF Browsers must be enabled for your organization. Until it is, the writes answer `403 feature_disabled`. See [get access](https://developers.openwaf.dev/browsers/#get-access).
- **An organization-wide `admin`** to create keys. Reading usage and sessions only needs `readonly`.
- **Node.js 18 or later** for the Playwright, Puppeteer and MCP examples.

## 1. Create a key

A browser key (`bdk_…`) authenticates your agent at `https://browsers.openwaf.dev`. An organization has up to 5 keys. Each key runs up to 2 browsers at the same time. The secret is shown once; afterwards the dashboard and the API only show its prefix (`bdk_` and 4 characters).

**Dashboard**

1. Open **Browsers** in the organization menu, then the **Keys** tab.
2. Choose **Create key** and give it a name, for example `ci-agent` (lowercase letters, digits and hyphens).
3. Copy the secret now. You will not see it again.

**CLI**

```sh
openwaf-cloud ctl browsers keys create ci-agent --reason "Browser for the CI agent"
```

The CLI shows what it will create and asks before applying it; then it prints the secret once. Add `--yes` to skip the question (required without a terminal), or `--dry-run` to only see it. See [get started with the CLI](https://developers.openwaf.dev/cli/get-started/) for the API address and token.
**API**

```sh
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/browsers/keys" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
  -H "X-Change-Reason: Browser for the CI agent" \
  -d '{"name": "ci-agent"}'
```

The `201` answer carries the key and its secret, with `Cache-Control: no-store`:

```json
{"key": {"id": "6f1c…", "name": "ci-agent", "prefix": "bdk_AbC1", "disabled": false,
         "created_at": "2026-10-29T10:00:00Z", "created_by": "…", "rotated_at": null},
 "secret": "bdk_…"}
```
**MCP**

Ask your assistant, connected to the [OpenWAF MCP server](https://developers.openwaf.dev/mcp/connect/) with an `admin` credential, to create a browser key. It calls `browser_keys_create` with `name` and a `reason`: first with `confirm=false` to check, then with `confirm=true`. The answer carries the secret once, so it passes through the conversation: rotate the key later if that matters to you.


Store the secret where your agent reads its secrets. The examples below read it from `OPENWAF_BROWSERS_KEY`:

```sh
read -rs OPENWAF_BROWSERS_KEY && export OPENWAF_BROWSERS_KEY   # paste bdk_…
```

## 2. Start a browser

```sh
curl -sS -X POST https://browsers.openwaf.dev/v1/browsers \
  -H "X-API-Key: $OPENWAF_BROWSERS_KEY" -H "Content-Type: application/json" \
  -d '{"metadata": {"task": "quickstart"}}'
```

Every field of the body is optional. The answer:

```json
{"id": "bs_…", "status": "running",
 "cdpUrl": "wss://browsers.openwaf.dev/cdp/…", "cdpHttpUrl": "https://browsers.openwaf.dev/…",
 "liveUrl": "https://browsers.openwaf.dev/…", "urlsExpireAt": "2026-10-29T11:00:00Z", …}
```

Open `liveUrl` in your own browser to watch. `cdpUrl` and `liveUrl` carry a token valid for 1 hour; `GET /v1/browsers/{id}` returns fresh ones.

If every slot of the beta is busy you get `429` with `{"code": "capacity_busy"}` and `Retry-After: 30`. If this key already runs 2 browsers you get `429` with `{"code": "key_concurrency"}`. See [errors](https://developers.openwaf.dev/browsers/reference/#data-plane-errors).

## 3. Drive it

**Playwright**

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

const api = "https://browsers.openwaf.dev/v1/browsers";
const headers = { "X-API-Key": process.env.OPENWAF_BROWSERS_KEY, "Content-Type": "application/json" };

const b = await (await fetch(api, { method: "POST", headers, body: "{}" })).json();
const browser = await chromium.connectOverCDP(b.cdpUrl);
const page = await browser.contexts()[0].newPage();
await page.goto("https://example.com");
console.log(await page.title());
await browser.close(); // disconnects; the browser keeps running until stopped or idle

await fetch(`${api}/${b.id}`, { method: "PATCH", headers, body: JSON.stringify({ action: "stop" }) });
```
**Puppeteer**

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

const api = "https://browsers.openwaf.dev/v1/browsers";
const headers = { "X-API-Key": process.env.OPENWAF_BROWSERS_KEY, "Content-Type": "application/json" };

const b = await (await fetch(api, { method: "POST", headers, body: "{}" })).json();
const browser = await puppeteer.connect({ browserWSEndpoint: b.cdpUrl });
const page = await browser.newPage();
await page.goto("https://example.com");
console.log(await page.title());
await browser.disconnect();

await fetch(`${api}/${b.id}`, { method: "PATCH", headers, body: JSON.stringify({ action: "stop" }) });
```
**Playwright MCP**

Start a browser with curl (step 2), then give its `cdpUrl` to the Playwright MCP server. Your assistant's browser tools then drive the hosted Chrome:

```sh
npx @playwright/mcp@latest --cdp-endpoint "wss://browsers.openwaf.dev/cdp/…"
```

In an MCP client configuration:

```json
{"mcpServers": {"browser": {"command": "npx",
  "args": ["@playwright/mcp@latest", "--cdp-endpoint", "wss://browsers.openwaf.dev/cdp/…"]}}}
```

The CDP token expires after 1 hour. For a longer task, get a fresh `cdpUrl` with `GET /v1/browsers/{id}` and restart the server.


## 4. Stop the browser

Closing the CDP connection does not stop a browser: it keeps counting browser time until it has had no CDP or live view client for 120 seconds. Stop it when the task is done:

```sh
curl -sS -X PATCH "https://browsers.openwaf.dev/v1/browsers/$ID" \
  -H "X-API-Key: $OPENWAF_BROWSERS_KEY" -H "Content-Type: application/json" \
  -d '{"action": "stop"}'
```

Then list what it left: the recording (if you asked for one) and the downloads, with links that expire.

```sh
curl -sS "https://browsers.openwaf.dev/v1/browsers/$ID/files" -H "X-API-Key: $OPENWAF_BROWSERS_KEY"
```

A member with the `tech` role or higher can also stop any running browser of the organization: **Browsers → Sessions → Stop**, `openwaf-cloud ctl browsers sessions stop ID --reason "…"` or the `browser_sessions_stop` MCP tool.

## Next steps

- [API reference](https://developers.openwaf.dev/browsers/reference/): every field of `POST /v1/browsers`, profiles, files and the egress log.
- [Limits and quotas](https://developers.openwaf.dev/browsers/limits/): what happens at 80 % and at 100 % of the quota.
- [Web Bot Auth](https://developers.openwaf.dev/browsers/web-bot-auth/): what the signature tells sites, and how to turn it off for one browser.
