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

# Session Lifecycle

Everything that happens between `POST /v1/sessions` and the session's end.

## Creation

```bash
curl -s https://api.chaser.sh/v1/sessions \
  -X POST \
  -H "Authorization: Bearer $CHASER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ttl_seconds": 1800
  }' | jq
```

Request fields:

| Field | Type | Default | Description |
|---|---|---|---|
| `profile` | string | default | Internal profile label. Omit unless instructed. |
| `ttl_seconds` | integer | `1800` | Seconds until auto-expiry. Range: 60–3600. |

Response (immediate, `status: "creating"`):

```json
{
  "id": "sess_019ec0d0-a88a-73f2-9741-8030dc82a854",
  "object": "session",
  "status": "creating",
  "cdp_url": "wss://cdp.chaser.sh/sessions/sess_019ec0d0-a88a-73f2-9741-8030dc82a854",
  "created_at": "2026-06-13T11:49:20.651182Z",
  "expires_at": "2026-06-13T12:19:20.650185Z"
}
```
The `cdp_url` is present from creation. Poll `GET /v1/sessions/{id}` until `status` is `"ready"` — provisioning takes roughly 15–25 seconds.

## Polling for readiness

```bash
while true; do
  STATUS=$(curl -s "https://api.chaser.sh/v1/sessions/$SESSION_ID" \
    -H "Authorization: Bearer $CHASER_KEY" | jq -r '.status')
  echo "Status: $STATUS"
  if [ "$STATUS" = "ready" ]; then
    break
  fi
  sleep 2
done
```

Or check a single GET:

```bash
```bash
curl -s "https://api.chaser.sh/v1/sessions/sess_019ec0d0-a88a-73f2-9741-8030dc82a854" \
  -H "Authorization: Bearer $CHASER_KEY" | jq '{status, cdp_url, device: .device}'
```

Response when ready:

```json
{
  "status": "ready",
  "cdp_url": "wss://cdp.chaser.sh/sessions/sess_019ec0d0-a88a-73f2-9741-8030dc82a854",
  "device": "Android 10 - Chrome 137 (Mobile)"
}
```

## Using the session

Once `status` is `"ready"`, connect and automate:

```typescript
import { chromium } from "patchright";

const res = await fetch("https://api.chaser.sh/v1/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CHASER_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ ttl_seconds: 1800 }),
});
const session = await res.json();

const browser = await chromium.connectOverCDP(session.cdp_url);
const page = browser.contexts()[0].pages()[0] ?? await browser.contexts()[0].newPage();
await page.goto("https://www.example.com");
```

## Deleting a session

Free the IP, stop the egress meter, and mark the session `ended_reason: "client_delete"`:

```bash
curl -s -X DELETE "https://api.chaser.sh/v1/sessions/sess_019ec0d0-a88a-73f2-9741-8030dc82a854" \
  -H "Authorization: Bearer $CHASER_KEY" \
  -w "\nHTTP %{http_code}\n"
```

Returns `204` on success. `409` if the session is already in a terminal state — that's fine, it's already stopped.

## End reasons

When a session closes, `ended_reason` tells you why:

| Reason | Meaning |
|---|---|
| `client_delete` | You deleted it |
| `ttl_expired` | Reached its `expires_at` timestamp |
| `idle_expired` | No CDP connection for too long |
| `abandoned_orphan` | Cleaned up after a platform restart |
| `substrate_down` | Infrastructure failure. No charge. |

## Egress accounting

Bytes are counted at the network level — all TCP bytes in and out of the session's proxy connection. This includes HTTP headers, TLS overhead, and any data your CDP client transfers.

The `bytes_up` and `bytes_down` fields on the session resource are updated in real time. The final charge is computed from `bytes_total = bytes_up + bytes_down` when the session closes.

A session that ends in `error` before reaching `ready` is not charged.