API reference
Every endpoint, every shape.
The Driftstack API is documented in a standard machine-readable format (an OpenAPI 3.1 spec), generated from the exact same validation rules (Zod schemas) the server itself enforces at runtime. There is no second source of truth — if a route exists, it's in the spec; if it's in the spec, the SDKs already know its exact shapes (typed bindings).
Interactive reference uses Scalar — try requests against your API key directly in the browser.
The living reference is docs.driftstack.dev — this page is a snapshot.
Surface map
Routes, grouped.
Sessions
- POST /v1/sessions
- GET /v1/sessions
- GET /v1/sessions/:id
- POST /v1/sessions/:id/navigate
- POST /v1/sessions/:id/interact
- POST /v1/sessions/:id/wait
- GET /v1/sessions/:id/state
- POST /v1/sessions/:id/capture
- DELETE /v1/sessions/:id
Agent sessions
- POST /v1/agent-sessions
- GET /v1/agent-sessions/:id
- POST /v1/agent-sessions/:id/message
- POST /v1/agent-sessions/:id/mode
- POST /v1/agent-sessions/:id/input-event
- POST /v1/agent-sessions/:id/takeover
- POST /v1/agent-sessions/:id/handback
- POST /v1/agent-sessions/:id/livekit-token
- GET /v1/agent-sessions/:id/transcript
- GET /v1/agent-sessions/:id/gui-control-key
- DELETE /v1/agent-sessions/:id
Recipes
- POST /v1/recipes
Profiles
- POST /v1/profiles
- GET /v1/profiles
- GET /v1/profiles/:id
- PATCH /v1/profiles/:id
- DELETE /v1/profiles/:id
API keys
- POST /v1/api-keys
- GET /v1/api-keys
- POST /v1/api-keys/:id/rotate
- DELETE /v1/api-keys/:id
Webhooks
- POST /v1/webhooks
- GET /v1/webhooks
- GET /v1/webhooks/:id
- PATCH /v1/webhooks/:id
- DELETE /v1/webhooks/:id
- POST /v1/webhooks/:id/rotate-secret
- POST /v1/webhooks/:id/test
- GET /v1/webhooks/:id/deliveries
- POST /v1/webhook-deliveries/:id/replay
Account
- GET /v1/account/me
- GET /v1/account/audit-log
- GET /v1/account/audit-log/export
- GET /v1/account/email-preferences
- PUT /v1/account/email-preferences
- GET /v1/account/rate-limits
- GET /v1/account/me/byok-anthropic-key
- PUT /v1/account/me/byok-anthropic-key
- DELETE /v1/account/me/byok-anthropic-key
- POST /v1/account/me/byok-anthropic-key/test
- GET /v1/account/me/bundled-llm-settings
- PATCH /v1/account/me/bundled-llm-settings
- GET /v1/account/me/bundled-llm-status
Team
- POST /v1/team/invites
- GET /v1/team/invites
- POST /v1/team/invites/accept
- GET /v1/team/members
- DELETE /v1/team/members/:id
Billing — crypto orders
- POST /v1/billing/crypto-checkout
- POST /v1/billing/crypto-checkout/quote
- GET /v1/billing/crypto-orders
- GET /v1/billing/crypto-orders/:id
- PATCH /v1/billing/crypto-orders/:id
- POST /v1/billing/crypto-orders/:id/cancel
- GET /v1/billing/crypto-orders/:id/receipt
- GET /v1/billing/crypto-orders/:id/receipt.txt
- GET /v1/billing/crypto-orders/:id/receipt.pdf
Status
- GET /v1/status
- GET /v1/status/stream
- GET /v1/status/sla
- POST /v1/status/subscribe
- GET /v1/status/subscribe/confirm
- GET /v1/status/subscribe/unsubscribe
Auth flows
- POST /v1/auth/signup
- POST /v1/auth/login
- POST /v1/auth/logout
- POST /v1/auth/verify-email
- POST /v1/auth/magic-link/request
- POST /v1/auth/magic-link/consume
- POST /v1/auth/password-reset/request
- POST /v1/auth/password-reset/confirm
- POST /v1/auth/refresh
Billing
- POST /v1/billing/checkout-session
- POST /v1/billing/portal-session
- GET /v1/billing
Common patterns
Three flows, four languages.
Most integrations are built on the same three operations: spin up a session, drive it, capture artifacts. Below: each one in cURL, TypeScript, Python, and Go. Copy-paste straight into a quickstart.
1. Create a session
The minimal "hello world" — provision an iPhone 17 Safari session, return its id. Default archetype if you don't pass one.
# cURL
curl -X POST https://api.driftstack.dev/v1/sessions \
-H "authorization: Bearer $DRIFTSTACK_API_KEY" \
-H "content-type: application/json" \
-d '{"archetype":"iphone17_ios18_7_safari26_4"}'
// TypeScript
import { Driftstack } from "@driftstack/sdk";
const client = new Driftstack({ apiKey: process.env.DRIFTSTACK_API_KEY });
const session = await client.sessions.create({
archetype: "iphone17_ios18_7_safari26_4",
});
console.log(session.id);
# Python
from driftstack import Driftstack
client = Driftstack(api_key=os.environ["DRIFTSTACK_API_KEY"])
session = client.sessions.create({"archetype": "iphone17_ios18_7_safari26_4"})
print(session.id)
// Go
client := driftstack.New(os.Getenv("DRIFTSTACK_API_KEY"))
session, err := client.Sessions.Create(ctx, &driftstack.CreateSessionRequest{
Archetype: "iphone17_ios18_7_safari26_4",
})
if err != nil { return err }
fmt.Println(session.ID)
2. Drive the session
Navigate, tap, wait. interact
handles taps, typing, scrolling, and key presses;
wait
blocks until a DOM condition is met or a timeout fires.
# cURL
curl -X POST $URL/navigate \
-d '{"url":"https://example.com"}' ...
curl -X POST $URL/interact \
-d '{"action":{"kind":"tap","selector":"button.cta"}}' ...
curl -X POST $URL/wait \
-d '{"condition":{"kind":"selector","selector":"main"}}' ...
// TypeScript
await client.sessions.navigate(session.id, { url: "https://example.com" });
await client.sessions.interact(session.id, {
action: { kind: "tap", selector: "button.cta" },
});
await client.sessions.wait(session.id, {
condition: { kind: "selector", selector: "main" },
timeout_ms: 5000,
});
# Python
client.sessions.navigate(session.id, {"url": "https://example.com"})
client.sessions.interact(session.id, {
"action": {"kind": "tap", "selector": "button.cta"},
})
client.sessions.wait(session.id, {
"condition": {"kind": "selector", "selector": "main"},
"timeout_ms": 5000,
})
// Go
_, err = client.Sessions.Navigate(ctx, session.ID, &driftstack.NavigateRequest{
URL: "https://example.com",
})
_, err = client.Sessions.Interact(ctx, session.ID, &driftstack.InteractRequest{
Action: driftstack.NewTapAction("button.cta"),
})
3. Capture a screenshot
capture takes one of three kinds:
screenshot,
dom_snapshot, or
pdf. The response carries the
file's contents directly as base64-encoded text (the
data field) — nothing is stored
server-side. Session video recording is on the
roadmap, not live yet.
# cURL
curl -X POST $URL/capture \
-d '{"kind":"screenshot"}' ...
# response.data is base64 PNG
// TypeScript
const shot = await client.sessions.capture(session.id, {
kind: "screenshot",
});
fs.writeFileSync("out.png", Buffer.from(shot.data, "base64"));
# Python
shot = client.sessions.capture(session.id, {"kind": "screenshot"})
Path("out.png").write_bytes(base64.b64decode(shot.data))
// Go
shot, _ := client.Sessions.Capture(ctx, session.ID, &driftstack.CaptureRequest{
Kind: driftstack.CaptureScreenshot,
})
data, _ := base64.StdEncoding.DecodeString(shot.Data)
os.WriteFile("out.png", data, 0644)
Error reference
What can go wrong, and what to do about it.
Every error follows the web standard for machine-readable
errors — RFC 9457
application/problem+json — and
carries a stable type URI: a
web link that identifies, and explains, the error kind. The
SDKs turn these into named error classes; the cURL caller
gets the same JSON straight.
| Status | Type URI | When | SDK class |
|---|---|---|---|
| 400 | errors.driftstack.dev/validation-failed | Zod schema mismatch on request body. | ValidationError |
| 401 | errors.driftstack.dev/unauthorized | API key missing, malformed, or revoked. | AuthError |
| 404 | errors.driftstack.dev/not-found | Resource id doesn't exist or isn't visible to this key. | NotFoundError |
| 409 | errors.driftstack.dev/conflict | Resource state precludes the operation (e.g. subscription already active). | ConflictError |
| 410 | errors.driftstack.dev/session-destroyed | Session was destroyed; create a new one. | SessionDestroyedError |
| 429 | errors.driftstack.dev/tier-limit | Account hit a tier cap (profile count, api-keys per account). | TierLimitError |
| 429 | errors.driftstack.dev/rate-limited | Per-account rate limit; retry-after header carries seconds. | RateLimitError (retryable) |
| 429 | errors.driftstack.dev/concurrency-limit | Your plan's limit on sessions running at the same time (the tier-bound concurrent-session cap) — end a session or wait for one to finish. | ConcurrencyLimitError |
| 500 | errors.driftstack.dev/internal | Server-side fault. Logged + alerted. | DriftstackError (kind: internal) (retryable) |
| 503 | errors.driftstack.dev/feature-unavailable | Feature disabled at deploy time (e.g. avatar upload without R2 bucket). | FeatureUnavailableError (NOT retryable) |
SDK consumers can use isRetryable(err)
(TypeScript) / equivalent predicates in Python + Go to filter
which errors to retry without re-implementing the mapping.
Spec posture
Stable contract. Versioned. Auto-generated.
- → Every endpoint has Zod schemas for request + response. The OpenAPI 3.1 spec is generated from the schemas — there is no second source of truth.
- → Every error case maps to the web standard for machine-readable errors — an RFC 9457
application/problem+jsonresponse with a stabletypeURI (a link that explains the error). - → Breaking changes ship under a new path version.
/v1stays stable;/v2would be a new prefix, not a silent shape change.