Session Lifecycle

View as Markdown

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

Creation

$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:

FieldTypeDefaultDescription
profilestringdefaultInternal profile label. Omit unless instructed.
ttl_secondsinteger1800Seconds until auto-expiry. Range: 60–3600.

Response (immediate, status: "creating"):

1{
2 "id": "sess_019ec0d0-a88a-73f2-9741-8030dc82a854",
3 "object": "session",
4 "status": "creating",
5 "cdp_url": "wss://cdp.chaser.sh/sessions/sess_019ec0d0-a88a-73f2-9741-8030dc82a854",
6 "created_at": "2026-06-13T11:49:20.651182Z",
7 "expires_at": "2026-06-13T12:19:20.650185Z"
8}

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

$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
>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:

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

Using the session

Once status is "ready", connect and automate:

1import { chromium } from "patchright";
2
3const res = await fetch("https://api.chaser.sh/v1/sessions", {
4 method: "POST",
5 headers: {
6 Authorization: `Bearer ${process.env.CHASER_KEY}`,
7 "Content-Type": "application/json",
8 },
9 body: JSON.stringify({ ttl_seconds: 1800 }),
10});
11const session = await res.json();
12
13const browser = await chromium.connectOverCDP(session.cdp_url);
14const page = browser.contexts()[0].pages()[0] ?? await browser.contexts()[0].newPage();
15await page.goto("https://www.example.com");

Deleting a session

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

$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:

ReasonMeaning
client_deleteYou deleted it
ttl_expiredReached its expires_at timestamp
idle_expiredNo CDP connection for too long
abandoned_orphanCleaned up after a platform restart
substrate_downInfrastructure 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.