Skip to navigation

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"):

{
"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

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:

{
"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:

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

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.