Skip to main content
DRIFTSTACK

Core routes, exact shapes.

The complete Driftstack API is documented in a standard machine-readable format (an OpenAPI 3.1 spec), generated from the same validation rules (Zod schemas) the server enforces at runtime. There is no second source of truth for public request and response shapes. This page is a curated map of common resources and runnable request patterns; the interactive reference carries the complete operation and schema catalog.

Interactive reference uses Scalar — try requests against your API key directly in the browser.

The complete living reference is docs.driftstack.dev — this page is a curated snapshot.

Common 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

Archetypes

  • GET /v1/archetypes

Read the public device, iOS, and Safari catalog without an API key. The catalog and create-payload generator reference shows how to resolve capabilities to a current archetype id.

Agent sessions

  • POST /v1/agent-sessions
  • GET /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

  • GET /v1/agent-sessions/:id/recipe-suggestion
  • POST /v1/recipes
  • GET /v1/recipes
  • GET /v1/recipes/:id
  • DELETE /v1/recipes/:id

Prefill recipe metadata from an agent session, then create, browse, inspect, or delete saved 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

Customer API keys, OAuth applications, and SDK automation require a paid tier. Free is supported through the desktop app's browser-authorized restricted device credential; it is not a customer key to paste into API samples.

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

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. Each block declares the environment values and imports it uses.

These API-key and SDK examples require a paid tier. On Free, sign in from the desktop app instead; browser authorization stores its restricted device credential automatically.

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
: "${DRIFTSTACK_API_KEY:?Set DRIFTSTACK_API_KEY}"
curl --fail-with-body -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 apiKey = process.env.DRIFTSTACK_API_KEY;
if (!apiKey) throw new Error("DRIFTSTACK_API_KEY is required");
const client = new Driftstack({ apiKey });
const session = await client.sessions.create({
  archetype: "iphone17_ios18_7_safari26_4",
});
console.log(session.id);
# Python
import os

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
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    driftstack "github.com/driftstackdev/driftstack-api/packages/sdk-go"
)

func main() {
    apiKey := os.Getenv("DRIFTSTACK_API_KEY")
    if apiKey == "" { log.Fatal("DRIFTSTACK_API_KEY is required") }
    client := driftstack.New(apiKey)
    defer client.Close()
    session, err := client.Sessions.Create(context.Background(), &driftstack.CreateSessionRequest{
        Archetype: "iphone17_ios18_7_safari26_4",
    })
    if err != nil { log.Fatal(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
: "${DRIFTSTACK_API_KEY:?Set DRIFTSTACK_API_KEY}"
: "${DRIFTSTACK_SESSION_ID:?Set DRIFTSTACK_SESSION_ID}"
SESSION_URL="https://api.driftstack.dev/v1/sessions/$DRIFTSTACK_SESSION_ID"
AUTH="authorization: Bearer $DRIFTSTACK_API_KEY"

curl --fail-with-body -X POST "$SESSION_URL/navigate" \
  -H "$AUTH" -H "content-type: application/json" \
  -d '{"url":"https://example.com"}'
curl --fail-with-body -X POST "$SESSION_URL/interact" \
  -H "$AUTH" -H "content-type: application/json" \
  -d '{"action":{"kind":"tap","selector":"button.cta"}}'
curl --fail-with-body -X POST "$SESSION_URL/wait" \
  -H "$AUTH" -H "content-type: application/json" \
  -d '{"condition":{"kind":"selector","selector":"main"},"timeout_ms":5000}'
// TypeScript
import { Driftstack } from "@driftstack/sdk";

const apiKey = process.env.DRIFTSTACK_API_KEY;
const sessionId = process.env.DRIFTSTACK_SESSION_ID;
if (!apiKey || !sessionId) throw new Error("Set DRIFTSTACK_API_KEY and DRIFTSTACK_SESSION_ID");
const client = new Driftstack({ apiKey });

await client.sessions.navigate(sessionId, { url: "https://example.com" });
await client.sessions.interact(sessionId, {
  action: { kind: "tap", selector: "button.cta" },
});
await client.sessions.wait(sessionId, {
  condition: { kind: "selector", selector: "main" },
  timeout_ms: 5000,
});
# Python
import os

from driftstack import Driftstack

client = Driftstack(api_key=os.environ["DRIFTSTACK_API_KEY"])
session_id = os.environ["DRIFTSTACK_SESSION_ID"]
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
package main

import (
    "context"
    "log"
    "os"

    driftstack "github.com/driftstackdev/driftstack-api/packages/sdk-go"
)

func main() {
    apiKey, sessionID := os.Getenv("DRIFTSTACK_API_KEY"), os.Getenv("DRIFTSTACK_SESSION_ID")
    if apiKey == "" || sessionID == "" { log.Fatal("set DRIFTSTACK_API_KEY and DRIFTSTACK_SESSION_ID") }
    client := driftstack.New(apiKey)
    defer client.Close()
    ctx := context.Background()
    if _, err := client.Sessions.Navigate(ctx, sessionID, &driftstack.NavigateRequest{URL: "https://example.com"}); err != nil { log.Fatal(err) }
    if _, err := client.Sessions.Interact(ctx, sessionID, &driftstack.InteractRequest{Action: driftstack.NewTapAction("button.cta")}); err != nil { log.Fatal(err) }
    if _, err := client.Sessions.Wait(ctx, sessionID, &driftstack.WaitRequest{Condition: driftstack.NewSelectorCondition("main"), TimeoutMS: 5000}); err != nil { log.Fatal(err) }
}

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.

# cURL
: "${DRIFTSTACK_API_KEY:?Set DRIFTSTACK_API_KEY}"
: "${DRIFTSTACK_SESSION_ID:?Set DRIFTSTACK_SESSION_ID}"
curl --fail-with-body -X POST "https://api.driftstack.dev/v1/sessions/$DRIFTSTACK_SESSION_ID/capture" \
  -H "authorization: Bearer $DRIFTSTACK_API_KEY" \
  -H "content-type: application/json" \
  -d '{"kind":"screenshot"}' \
  | jq -r .data | openssl base64 -d -A > out.png
// TypeScript
import { writeFileSync } from "node:fs";
import { Driftstack } from "@driftstack/sdk";

const apiKey = process.env.DRIFTSTACK_API_KEY;
const sessionId = process.env.DRIFTSTACK_SESSION_ID;
if (!apiKey || !sessionId) throw new Error("Set DRIFTSTACK_API_KEY and DRIFTSTACK_SESSION_ID");
const client = new Driftstack({ apiKey });
const shot = await client.sessions.capture(sessionId, {
  kind: "screenshot",
});
writeFileSync("out.png", Buffer.from(shot.data, "base64"));
# Python
import base64
import os
from pathlib import Path

from driftstack import Driftstack

client = Driftstack(api_key=os.environ["DRIFTSTACK_API_KEY"])
shot = client.sessions.capture(os.environ["DRIFTSTACK_SESSION_ID"], {"kind": "screenshot"})
Path("out.png").write_bytes(base64.b64decode(shot.data))
// Go
package main

import (
    "context"
    "encoding/base64"
    "log"
    "os"

    driftstack "github.com/driftstackdev/driftstack-api/packages/sdk-go"
)

func main() {
    apiKey, sessionID := os.Getenv("DRIFTSTACK_API_KEY"), os.Getenv("DRIFTSTACK_SESSION_ID")
    if apiKey == "" || sessionID == "" { log.Fatal("set DRIFTSTACK_API_KEY and DRIFTSTACK_SESSION_ID") }
    client := driftstack.New(apiKey)
    defer client.Close()
    shot, err := client.Sessions.Capture(context.Background(), sessionID, &driftstack.CaptureRequest{Kind: driftstack.CaptureScreenshot})
    if err != nil { log.Fatal(err) }
    data, err := base64.StdEncoding.DecodeString(shot.Data)
    if err != nil { log.Fatal(err) }
    if err := os.WriteFile("out.png", data, 0o644); err != nil { log.Fatal(err) }
}

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. InternalError (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.

Stable contract. Versioned. Auto-generated.

  • Public request and response shapes are defined with Zod schemas. The OpenAPI 3.1 spec is generated from those 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+json response with a stable type URI (a link that explains the error).
  • Breaking changes ship under a new path version. /v1 stays stable; /v2 would be a new prefix, not a silent shape change.