Skip to main content
Driftstack DRIFTSTACK

Python SDK quickstart

The driftstack Python package wraps the same REST surface as the curl quickstart. It supports both synchronous and asyncio call-styles from a single client, so it slots into Django, Flask, FastAPI, or a plain script equally well.

1. Install

pip install driftstack-sdk
# or, with poetry / uv:
poetry add driftstack-sdk
uv add driftstack-sdk

Python ≥ 3.10 is supported. The package ships type stubs; mypy --strict works against the public surface without further config.

2. Construct the client

import os
from driftstack import Driftstack

client = Driftstack(api_key=os.environ["DRIFTSTACK_API_KEY"])
# base_url kwarg overrides the default https://api.driftstack.dev
# for staging or self-hosted deployments.

Reuse one client across your process. It is internally pooled via httpx and safe for concurrent use across threads or asyncio tasks.

3. Start a session

session = client.sessions.create({
    "archetype": "default",
    "purpose": "production_customer",
    "label": "demo",
})
print(session["id"], session["status"], session["archetype"])

The session-create call does not take a target URL. Drive the session to a URL with client.sessions.navigate(session_id, {"url": "https://example.com"}). Session ids are prefixed ses_.

4. Drive and finish the session

There is no built-in “wait until terminal” helper — drive the session with the methods that fit your workflow (navigate, interact, wait, capture), then destroy when done. The wait method blocks on a server-side condition (selector, URL, time):

client.sessions.navigate(session["id"], {"url": "https://example.com"})
client.sessions.wait(session["id"], {"condition": {"kind": "time", "ms": 1000}})
shot = client.sessions.capture(session["id"], {"kind": "screenshot"})
print("captured", shot["byte_size"], "bytes")
client.sessions.destroy(session["id"])  # idempotent

For batch workloads where you don't need to drive the session moment-to-moment, prefer webhooks over polling.

5. Paginate over historical sessions

# Sync — lazy iterator over cursor pages.
for s in client.sessions.iterate(limit=50):
    if s["status"] == "destroyed":
        print(s["id"])

# Async — same shape with AsyncDriftstack.
import asyncio
from driftstack import AsyncDriftstack

async def main() -> None:
    async with AsyncDriftstack(api_key=os.environ["DRIFTSTACK_API_KEY"]) as ac:
        async for s in ac.sessions.iterate(limit=50):
            print(s["id"])

asyncio.run(main())

iterate walks every cursor page and stops on next_cursor: null. The single-page form is client.sessions.list({"limit": ..., "cursor": ...}).

Error handling

Every SDK method raises a DriftstackError subclass on non-2xx responses. The exception carries status, problem_type (the RFC 7807 URI), message, and problem (the full parsed problem dict, e.g. exc.problem["retry_after_seconds"]). Branch on the subclass for clean dispatch:

from driftstack import DriftstackError
from driftstack.errors import RateLimitError, ValidationError

try:
    client.sessions.create({"archetype": "definitely-not-a-real-archetype"})
except ValidationError as exc:
    # per-field issues live on exc.problem
    ...
except RateLimitError as exc:
    # exc.problem["retry_after_seconds"] is set
    ...
    else:
        raise

Sync ↔ async parity

Every method on Driftstack has a mirror on AsyncDriftstack. The async variant returns coroutines and async iterators; the sync variant blocks the caller. Pick the one that fits your runtime; do not mix them in a single call-chain.

Where to go next

Need help?

[email protected]. We respond within one business day.