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
- Open app.driftstack.dev/api-keys.
- 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.
- 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-Keyheader 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/webhookssubscribes an HTTPS endpoint of yours to events likesession.completedandsession.failed. See Webhook endpoints.
Next steps
- SDK quickstart — the same flow in TypeScript, Python, or Go.
- Sessions reference — every session endpoint and field.
- Concurrency & backpressure — caps per tier and how to back off.
- Errors — the stable problem types every endpoint returns.
- Rate limits — request-rate buckets, separate from concurrency.
Stuck? Email [email protected]
with your account ID (acc_…) and the x-request-id header from any
error response.