Skip to main content

Sessions

A session is one running browser instance Driftstack manages for you. This page is the reference for the /v1/sessions surface; for the 60-second walkthrough see api-quickstart.

On hosted Driftstack, a create needs the proxy_id of one of your saved proxies, or a profile_id whose profile is bound to one (without either: 422 proxy-required, and nothing is created), and the step-by-step calls on a session (navigate, interact, wait, extract, state, capture) answer 503 driver-not-integrated and run nothing. To drive pages on hosted, start an agent session (POST /v1/agent-sessions with the same proxy_id) and send your own steps with POST /v1/agent-sessions/<id>/steps; see step-by-step operations on the hosted service. The step calls below run on a self-hosted deployment.

Lifecycle

StateWhat it means
creatingDriftstack is starting the browser.
readyBrowser ready; the session URL is reachable.
busySession is actively running an automation step.
destroyedNormal end-of-life; the session has ended.
erroredAbnormal termination; the session cannot recover.

Transitions: creating → ready → busy → destroyed is the happy path. errored is terminal; the session cannot recover.

A session also ends on its own: after 30 minutes without activity, or after 4 hours (20 minutes on the free trial). Create a new one to carry on.

POST /v1/sessions

POST /v1/sessions
Authorization: Bearer ds_live_…
Content-Type: application/json

{
  "archetype": "iphone15_ios18_7_safari26_5", ← optional, id from GET /v1/archetypes
  "purpose": "production_customer",        ← optional, default applied server-side
  "label": "ticket-JIRA-1234",             ← optional, ≤120 chars
  "metadata": { "ticket": "JIRA-1234" },   ← optional, surfaced in webhooks
  "profile_id": "prof_01HV…",              ← optional, binds session to a profile
  "behavioral_profile": "regular",         ← optional, persona: casual|regular|power_user
  "proxy_id": "6f1c2a9e-…"                 ← a saved proxy; hosted Driftstack needs one, or a profile_id bound to one
}

→ 201 Created
{
  "id": "ses_…",
  "account_id": "acc_…",
  "api_key_id": "key_…",
  "status": "ready",
  "archetype": "iphone15_ios18_7_safari26_5",
  "purpose": "production_customer",
  "label": "ticket-JIRA-1234",
  "metadata": { "ticket": "JIRA-1234" },
  "created_at": "2026-05-12T12:00:00.000Z",
  "updated_at": "2026-05-12T12:00:00.000Z",
  "last_state_at": null,
  "destroyed_at": null
}

The response is a flat session object — no {"session": …} envelope. Session ids are prefixed ses_. The create call returns only after the browser is ready, so an intermediate creating state is not directly observable. Poll GET /v1/sessions/:id for later state changes. The session.completed and session.failed webhooks signal clean or errored end-of-life, not readiness.

Self-hosted only: on hosted Driftstack the navigate and capture calls below answer 503 driver-not-integrated; use navigate and capture steps in an agent session there.

The create call does not take a target URL. Drive the session to a URL after it reaches ready with POST /v1/sessions/<id>/navigate:

POST /v1/sessions/ses_…/navigate
Authorization: Bearer ds_live_…
Content-Type: application/json

{ "url": "https://example.com", "wait_until": "load" }

GET /v1/sessions/:id

GET /v1/sessions/ses_…
Authorization: Bearer ds_live_…

→ 200 OK
{
  "id": "ses_…",
  "account_id": "acc_…",
  "api_key_id": "key_…",
  "status": "busy",
  "archetype": "iphone15_ios18_7_safari26_5",
  "purpose": "production_customer",
  "label": "ticket-JIRA-1234",
  "metadata": { "ticket": "JIRA-1234" },
  "created_at": "2026-05-12T12:00:00.000Z",
  "updated_at": "2026-05-12T12:00:08.000Z",
  "last_state_at": "2026-05-12T12:00:08.000Z",
  "destroyed_at": null
}

GET /v1/sessions — list

GET /v1/sessions?limit=25
Authorization: Bearer ds_live_…

→ 200 OK
{
  "data": [ /* Session objects, newest first */ ],
  "has_more": true,
  "next_cursor": "eyJ0aWQiOi…"   ← null when no more pages
}

Cursor pagination. Pass the previous response's next_cursor as ?cursor= on the next request. Default page size is 50; max 100.

POST /v1/sessions/:id/capture

POST /v1/sessions/ses_…/capture
Authorization: Bearer ds_live_…
Content-Type: application/json

{ "kind": "screenshot" }

→ 200 OK
{
  "kind": "screenshot",
  "data": "<base64-encoded bytes>",
  "encoding": "base64",
  "byte_size": 184320,
  "duration_ms": 412
}

Captures return inline base64 bytes — there is no presigned URL. kind selects screenshot, dom_snapshot, or pdf. Decode data on the client according to encoding.

DELETE /v1/sessions/:id

DELETE /v1/sessions/ses_…
Authorization: Bearer ds_live_…

→ 204 No Content

Ends the session immediately and transitions it to destroyed. Idempotent — DELETEing an already-destroyed session also returns 204.

Concurrent session limits

Each tier caps the number of sessions you can have in the live states (creating, ready, busy) at once. See rate-limits + the concurrency doc for the per-tier table. Hitting the cap returns 429 Too Many Requests with the concurrency-limit RFC 9457 problem type — wait for an existing session to finish or upgrade your tier.

Webhooks

Subscribe to session.completed + session.failed instead of polling. See /docs/webhooks for the delivery format + signature verification, and the event-types table for the complete list of subscribable events.

Choosing a device (archetype)

The archetype field identifies an exact device, iOS, and Safari combination from the live GET /v1/archetypes catalog. Saved profiles (profile_id) pin one returned id for reuse. For a one-shot session, omit the field and the server picks a randomly chosen iPhone model from those your plan includes that are open for new profiles, which can differ from session to session; the session's archetype_source reads random.

Support

[email protected].