Driftstack DRIFTSTACK docs
Docs

Quickstart (curl)

This page runs your first Driftstack session using only curl — no SDK, no install step. Every Driftstack feature is a plain HTTPS call, so this is also the fastest way to see exactly what goes over the wire. If you’d rather start in TypeScript, Python, or Go, use the SDK quickstart instead.

You will need a Driftstack account (sign up or sign in) and curl.

1. Get an API key

  1. Open app.driftstack.dev/api-keys.
  2. Click Create key, give it a name, and copy the value. The full key is shown once — Driftstack stores only a hash, so if you lose it you revoke it and mint a new one.
  3. Export it in your shell:
export DRIFTSTACK_API_KEY="ds_live_…"

Keys on paid tiers start with ds_live_; free-tier accounts get ds_test_ keys. Both authenticate the same way — an Authorization: Bearer header on every call.

Pick the narrowest scopes that fit the job — a scope is a permission attached to the key. write:sessions is enough for everything on this page; read + write cover a typical production app; keep account_owner (full account control) for dashboards, not runtime automation. Full list: API key scopes.

2. Check the key works

curl -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  https://api.driftstack.dev/v1/account/me
{
  "id": "acc_…",
  "email": "[email protected]",
  "name": "Your Name",
  "tier": "api_starter",
  "status": "active",
  "timezone": "Europe/Amsterdam",
  "mfa_enrolled": false,
  "concurrent_session_cap": 2,
  "concurrent_session_active": 0,
  "profile_cap": 25,
  "profile_count": 0,
  "teams": []
}

A 200 with a flat account object (no wrapper envelope) means the key is good. A 401 means the key is wrong, malformed, or revoked — check the dashboard. concurrent_session_cap / concurrent_session_active are worth noting now: they’re the concurrency budget the next step draws from.

3. Create a session

curl -X POST \
  -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "first-session" }' \
  https://api.driftstack.dev/v1/sessions
{
  "id": "ses_…",
  "account_id": "acc_…",
  "api_key_id": "key_…",
  "status": "ready",
  "archetype": "iphone17_ios18_7_safari26_4",
  "purpose": "production_customer",
  "label": "first-session",
  "metadata": null,
  "egress_capabilities": null,
  "egress_capability_report": null,
  "created_at": "2026-07-07T12:00:00.000Z",
  "updated_at": "2026-07-07T12:00:01.000Z",
  "last_state_at": null,
  "destroyed_at": null
}

The 201 response is the session record, already ready — the create call holds until the phone-browser runtime is allocated and responding, so there’s nothing to poll before you drive it. All body fields are optional: omitting archetype (the device + OS + browser identity the session presents) gives you the locked default shown above. Sessions can also start from a saved profile via profile_id.

Every session occupies one concurrent slot until you destroy it. Hitting your tier’s cap returns a 429 — see Concurrency & backpressure.

4. Drive it

Point the session at a URL:

curl -X POST \
  -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com" }' \
  https://api.driftstack.dev/v1/sessions/ses_…/navigate
{
  "url": "https://example.com",
  "final_url": "https://example.com/",
  "status": 200,
  "duration_ms": 1412
}

Then read the live page state — URL, title, cookies, storage, and a page_state block that tells you whether the page loaded or errored:

curl -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  https://api.driftstack.dev/v1/sessions/ses_…/state
{
  "url": "https://example.com/",
  "title": "Example Domain",
  "cookies": [],
  "local_storage": {},
  "page_state": { "state": "loaded" },
  "captured_at": "2026-07-07T12:00:04.000Z"
}

The full action surface — interact (tap / type / scroll / press), wait, extract, search, login — is on the Sessions reference.

5. Capture a screenshot

curl -X POST \
  -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "screenshot" }' \
  https://api.driftstack.dev/v1/sessions/ses_…/capture
{
  "kind": "screenshot",
  "data": "iVBORw0KGgo…",
  "encoding": "base64",
  "byte_size": 184320,
  "duration_ms": 412
}

Captures return the bytes inline — there is no download URL to fetch. For a screenshot, data is the PNG base64-encoded; decode it before saving:

curl -s -X POST \
  -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "kind": "screenshot" }' \
  https://api.driftstack.dev/v1/sessions/ses_…/capture \
  | python3 -c 'import sys, json, base64; open("shot.png","wb").write(base64.b64decode(json.load(sys.stdin)["data"]))'

kind can also be dom_snapshot (the serialised page HTML, returned as plain text with encoding: "utf8") or pdf. Screenshots cap at 4 MiB, PDFs at 8 MiB.

6. Destroy the session

curl -X DELETE \
  -H "Authorization: Bearer $DRIFTSTACK_API_KEY" \
  https://api.driftstack.dev/v1/sessions/ses_…

Returns 204 No Content, frees the concurrent slot immediately, and is idempotent — destroying an already-destroyed session is a no-op. Always destroy when you’re done: a forgotten session keeps holding a slot (on the free tier it’s auto-destroyed after the 20-minute session cap; paid tiers have no time cap, so the slot stays held until you release it).

Two habits worth starting now

  • Send an Idempotency-Key header on create-style POSTs. If the network drops mid-request, the retry replays the original response instead of minting a duplicate session. See Idempotency keys.
  • Prefer webhooks to polling. POST /v1/webhooks subscribes an HTTPS endpoint of yours to events like session.completed and session.failed. See Webhook endpoints.

Next steps

Stuck? Email [email protected] with your account ID (acc_…) and the x-request-id header from any error response.