# Quickstart

> Create an API key, open a profile on one of your machines, control it with Playwright and stop it again.

This walk-through uses the HTTP API directly so every step is visible. The same steps exist in the [SDK](https://browlix.com/en/docs/sdk), the [CLI](https://browlix.com/en/docs/cli) and as [MCP tools](https://browlix.com/en/docs/mcp).

You need a Browlix workspace with at least one paired machine that is online (Workspace → Machines → Pair a machine). Step 6 must run **on that machine** — see [Where your code runs](https://browlix.com/en/docs/where-code-runs).

## Before you start

Install the client for the language you use (Node 20 or later, or Python 3.9 or later). The Playwright package is only the client; it does not need its own browsers, because Browlix opens the browser:

```bash tab=TypeScript
npm i https://browlix.com/api/public/sdk/v1/download/latest playwright
```

```bash tab=Python
pip install requests playwright
```

The TypeScript snippets form one ES module with top-level `await`: save them as `quickstart.mts` and run `node quickstart.mts` (Node 22.18 or later runs TypeScript directly; on older Node use `npx tsx quickstart.mts`). The curl snippets are bash. On Ubuntu and Debian the system Python refuses a global `pip install`: create a virtual environment first with `python3 -m venv .venv && . .venv/bin/activate` (package `python3-venv`).

## 1. Create an API key

In the web app open **Settings → API keys**, create a key with the scopes you need and copy it — the secret is shown once. Keep it in an environment variable, never in code:

```bash
export BROWLIX_API_KEY="bxk_…"
```

## 2. Check the key

```bash tab=curl
curl https://browlix.com/api/public/v1/workspace \
  -H "Authorization: Bearer $BROWLIX_API_KEY"
```

```ts tab=TypeScript
import { Browlix } from "@browlix/sdk";

const bx = new Browlix({ apiKey: process.env.BROWLIX_API_KEY! });
const { data: workspace } = await bx.call("workspace.get");
console.log(workspace);
```

```python tab=Python
import os, requests

API = "https://browlix.com/api/public/v1"
H = {"Authorization": f"Bearer {os.environ['BROWLIX_API_KEY']}"}
print(requests.get(f"{API}/workspace", headers=H, timeout=60).json())
```

Every response carries a `correlation_id`; quote it when you report a problem.

## 3. Pick a machine

```bash tab=curl
curl https://browlix.com/api/public/v1/devices \
  -H "Authorization: Bearer $BROWLIX_API_KEY"
# export DEVICE_ID=<data[0].id>
```

```ts tab=TypeScript
const { data: devices } = await bx.call<"devices.list", { id: string; name: string }[]>("devices.list");
const deviceId = devices[0]!.id;
```

```python tab=Python
devices = requests.get(f"{API}/devices", headers=H, timeout=60).json()["data"]
device_id = devices[0]["id"]
```

Use a machine whose state is online; the list also shows its operating system and free session capacity.

## 4. Create a profile

Writes are **commands**: you send your own `command_id` (a UUID) so a retry can never create a second profile. See [Commands and receipts](https://browlix.com/en/docs/commands-receipts).

```bash tab=curl
curl -X POST https://browlix.com/api/public/v1/profiles \
  -H "Authorization: Bearer $BROWLIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"command_id\":\"$(uuidgen)\",\"profile\":{\"name\":\"Quickstart\",\"config\":{\"os\":\"linux\"}}}"
# export PROFILE_ID=<data.profile_id>
```

```ts tab=TypeScript
const created = await bx.call<"profiles.create", { profile_id: string }>("profiles.create", {
  body: { profile: { name: "Quickstart", config: { os: "linux" } } },
});
const profileId = created.data.profile_id; // the SDK generated and sent a command_id for you
```

```python tab=Python
import uuid

created = requests.post(f"{API}/profiles", headers=H, timeout=60, json={
    "command_id": str(uuid.uuid4()),
    "profile": {"name": "Quickstart", "config": {"os": "linux"}},
}).json()
profile_id = created["data"]["profile_id"]
```

Set `os` to the operating system of the machine you picked (`windows` or `linux`): a profile is kept consistent with the machine it runs on.

## 5. Open the profile

```bash tab=curl
curl -X POST https://browlix.com/api/public/v1/sessions \
  -H "Authorization: Bearer $BROWLIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"command_id\":\"$(uuidgen)\",\"profile_id\":\"$PROFILE_ID\",\"device_id\":\"$DEVICE_ID\"}"

# export SESSION_ID=<data.id>, then poll until data.lifecycle is "ready"
curl https://browlix.com/api/public/v1/sessions/$SESSION_ID \
  -H "Authorization: Bearer $BROWLIX_API_KEY"
```

```ts tab=TypeScript
const session = await bx.startSession({ profileId, deviceId, wait: { timeoutMs: 180_000 } });
const sessionId = session.data.id; // resolved once lifecycle is "ready"
```

```python tab=Python
import time

started = requests.post(f"{API}/sessions", headers=H, timeout=60, json={
    "command_id": str(uuid.uuid4()), "profile_id": profile_id, "device_id": device_id,
}).json()
session_id = started["data"]["id"]
while True:
    s = requests.get(f"{API}/sessions/{session_id}", headers=H, timeout=60).json()["data"]
    if s["lifecycle"] == "ready":
        break
    if s["lifecycle"] in ("failed", "recovery_required", "closed"):
        raise SystemExit(f"session is {s['lifecycle']}")
    time.sleep(1)
```

A profile has at most one live session. Opening it again answers `BUSY` with the open session's id.

## 6. Attach Playwright (on that machine)

`sessions.attach` returns a short-lived ticket and the address of the machine's local broker, `ws://127.0.0.1:<port>/cdp`. Run this on the machine that holds the session:

```ts tab=TypeScript
import { chromium } from "playwright";

const a = await bx.attach(sessionId, { ttlSeconds: 300 });
const browser = await chromium.connectOverCDP(a.endpoint, { headers: a.headers });
const page = browser.contexts()[0]!.pages()[0] ?? (await browser.contexts()[0]!.newPage());
await page.goto("https://example.com");
console.log(await page.title());
await browser.close(); // disconnects Playwright; the session keeps running
```

```python tab=Python
from playwright.sync_api import sync_playwright

a = requests.post(f"{API}/sessions/{session_id}/attach", headers=H, timeout=60,
                  json={"ttl_seconds": 300}).json()["data"]
with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(a["endpoint"], headers={"Authorization": f"Bearer {a['ticket']}"})
    context = browser.contexts[0]
    page = context.pages[0] if context.pages else context.new_page()
    page.goto("https://example.com")
    print(page.title())
    browser.close()
```

The ticket is valid for one session, for `ttl_seconds` (30–900, default 300). Ask for a new one when it expires.

## 7. Stop the session

```bash tab=curl
curl -X POST https://browlix.com/api/public/v1/sessions/$SESSION_ID/stop \
  -H "Authorization: Bearer $BROWLIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"command_id\":\"$(uuidgen)\"}"
```

```ts tab=TypeScript
await bx.stopSession(sessionId); // the only SDK call that ends a session
```

```python tab=Python
requests.post(f"{API}/sessions/{session_id}/stop", headers=H, timeout=60,
              json={"command_id": str(uuid.uuid4())})
```

The session is reported closed only after the machine proves no browser process is left.

## Next steps

- [Sessions](https://browlix.com/en/docs/sessions) — every lifecycle state and what to do in each.
- [Webhooks](https://browlix.com/en/docs/webhooks) — get `session.ready` pushed instead of polling.
- [Errors](https://browlix.com/en/docs/errors) — the typed codes and which ones are safe to retry.
