# Bắt đầu nhanh

> Tạo khoá API, mở một hồ sơ trên máy của bạn, điều khiển bằng Playwright rồi dừng lại.

Hướng dẫn này gọi thẳng HTTP API để thấy rõ từng bước. Các bước tương tự có trong [SDK](https://browlix.com/docs/sdk), [CLI](https://browlix.com/docs/cli) và dưới dạng [tool MCP](https://browlix.com/docs/mcp).

Bạn cần một workspace Browlix có ít nhất một máy đã ghép nối và đang trực tuyến (Workspace → Máy chạy → Ghép nối máy). Bước 6 phải chạy **trên chính máy đó** — xem [Code chạy ở đâu](https://browlix.com/docs/where-code-runs).

## Trước khi bắt đầu

Cài thư viện cho ngôn ngữ bạn dùng (Node 20 trở lên hoặc Python 3.9 trở lên). Gói Playwright chỉ là phần điều khiển, không cần tải trình duyệt riêng vì Browlix mở trình duyệt:

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

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

Các đoạn TypeScript ghép thành một ES module có `await` ở cấp ngoài cùng: lưu thành `quickstart.mts` rồi chạy `node quickstart.mts` (Node 22.18 trở lên chạy thẳng TypeScript; Node cũ hơn dùng `npx tsx quickstart.mts`). Các đoạn curl là bash. Trên Ubuntu và Debian, Python hệ thống từ chối `pip install` toàn máy: tạo môi trường ảo trước bằng `python3 -m venv .venv && . .venv/bin/activate` (gói `python3-venv`).

## 1. Tạo khoá API

Trên web mở **Cài đặt → Khoá API**, tạo khoá với phạm vi quyền cần dùng và sao chép — phần bí mật chỉ hiện một lần. Giữ khoá trong biến môi trường, không viết vào code:

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

## 2. Kiểm tra khoá

```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())
```

Mọi phản hồi có `correlation_id`; hãy gửi kèm mã này khi báo lỗi.

## 3. Chọn máy

```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"]
```

Chọn máy đang trực tuyến; danh sách cũng cho biết hệ điều hành và số phiên còn mở được.

## 4. Tạo hồ sơ

Mọi thao tác ghi là **lệnh**: bạn tự gửi `command_id` (một UUID) để thử lại không bao giờ tạo hồ sơ thứ hai. Xem [Lệnh và biên nhận](https://browlix.com/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; // SDK đã tự tạo và gửi command_id
```

```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"]
```

Đặt `os` đúng hệ điều hành của máy đã chọn (`windows` hoặc `linux`): hồ sơ được giữ nhất quán với máy chạy nó.

## 5. Mở hồ sơ

```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>, rồi hỏi lại tới khi data.lifecycle là "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; // trả về khi lifecycle là "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)
```

Mỗi hồ sơ chỉ có tối đa một phiên đang chạy. Mở lại sẽ nhận `BUSY` kèm mã phiên đang mở.

## 6. Gắn Playwright (trên máy đó)

`sessions.attach` trả về vé ngắn hạn và địa chỉ broker cục bộ của máy, `ws://127.0.0.1:<port>/cdp`. Chạy đoạn này trên máy đang giữ phiên:

```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(); // ngắt Playwright; phiên vẫn chạy
```

```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()
```

Vé chỉ dùng cho một phiên, trong `ttl_seconds` (30–900, mặc định 300). Hết hạn thì xin vé mới.

## 7. Dừng phiên

```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); // lệnh SDK duy nhất kết thúc phiên
```

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

Phiên chỉ được báo đã đóng khi máy chứng minh không còn tiến trình trình duyệt nào.

## Tiếp theo

- [Phiên trình duyệt](https://browlix.com/docs/sessions) — mọi trạng thái và nên làm gì ở từng trạng thái.
- [Webhook](https://browlix.com/docs/webhooks) — nhận `session.ready` thay vì hỏi lại liên tục.
- [Lỗi](https://browlix.com/docs/errors) — các mã lỗi và mã nào an toàn để thử lại.
