Skip to navigation

Quickstart

View as Markdown

Get a running session, connect to it, take a screenshot, and tear it down — all in under five minutes.

Prerequisites

An API key from the Chaser dashboard. Sign up, create a workspace, and generate a key under API Keys. Set it as an environment variable:

export CHASER_KEY="cha_live_..."

Step 1 — Create a session

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

Response:

{
"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-13T11:59:20.650185Z"
}

The session is ready when status is "ready". The cdp_url is a standard WebSocket endpoint.

Step 2 — Connect and automate

Use patchright (a drop-in Playwright replacement) or any CDP client:

// npm i patchright
import { chromium } from "patchright";
const browser = await chromium.connectOverCDP(cdpUrl);
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();
await page.goto("https://www.cloudflare.com");
const title = await title.innerText();
console.log(title);

Or with Python:

# pip install patchright
from patchright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(cdp_url)
context = browser.contexts[0]
page = context.pages[0] or context.new_page()
page.goto("https://www.cloudflare.com")
print(page.title())

Why patchright? The Chromium bundled with stock Playwright and Puppeteer ships with known automation artifacts — navigator.webdriver === true, missing chrome.runtime, and other detectable properties. Patchright patches these at the browser level so the session looks like an unmodified user browser.

Step 3 — Take a screenshot

Use Chaser’s screenshot endpoint rather than page.screenshot(). The endpoint captures the framebuffer directly and avoids compositor-readback issues:

curl -s https://api.chaser.sh/v1/sessions/sess_019ec0b4-b174-7e90-91b3-02032470cb24/screenshot \
-X POST \
-H "Authorization: Bearer $CHASER_KEY" \
-o page.png

The response is a raw PNG. From code:

const png = await fetch(
`https://api.chaser.sh/v1/sessions/${sessionId}/screenshot`,
{
method: "POST",
headers: { Authorization: `Bearer ${process.env.CHASER_KEY}` },
}
).then((r) => r.arrayBuffer());
writeFileSync("page.png", Buffer.from(png));

Step 4 — Tear down

curl -s https://api.chaser.sh/v1/sessions/sess_019ec0b4-b174-7e90-91b3-02032470cb24 \
-X DELETE \
-H "Authorization: Bearer $CHASER_KEY"

Deleting a session is idempotent. If it already expired or was deleted, you get a 409 session_not_open — that’s fine. The session stops accumulating egress the moment it’s deleted.

Sessions also auto-expire at the expires_at timestamp. Set ttl_seconds on creation (default: 1800, max: 3600) to control how long a session lives without explicit deletion.

What’s next