TypeScript SDK quickstart
The @driftstack/sdk package wraps the same REST
surface as the curl quickstart,
with end-to-end types generated from the OpenAPI spec. If you're
already in a TS codebase, this is the recommended way to call
Driftstack.
1. Install
# bun
bun add @driftstack/sdk
# npm
npm install @driftstack/sdk
# pnpm
pnpm add @driftstack/sdk
Node ≥ 20, Bun ≥ 1.1, and Deno ≥ 1.40 are supported. The SDK
ships ESM-only; if you're still on CommonJS, use a dynamic
import().
2. Construct the client
import { Driftstack } from '@driftstack/sdk';
const client = new Driftstack({
apiKey: process.env.DRIFTSTACK_API_KEY!,
// baseUrl defaults to https://api.driftstack.dev; override for
// self-hosted deployments or staging.
}); The constructor does not make any network calls. Reuse one client across your process — it is internally pooled and safe for concurrent use.
3. Start a session
const session = await client.sessions.create({
archetype: 'iphone17_ios18_7_safari26_4',
purpose: 'production_customer',
label: 'demo',
});
console.log(session.id, session.status, session.archetype);
The return is a flat Session object (no envelope).
TypeScript narrows session.status through the
lifecycle (creating → ready →
busy → destroyed /
errored). The session creation call does NOT take
a target URL — drive the session to a URL with
client.sessions.navigate():
await client.sessions.navigate(session.id, {
url: 'https://example.com',
}); 4. Poll the session until it terminates
There is no built-in waitUntil helper — drive the
lifecycle with the methods that fit your workflow
(navigate, interact,
wait, capture), then either call
destroy when you're done or poll
list / fetch by id to observe terminal status:
// Drive the session.
await client.sessions.navigate(session.id, { url: 'https://example.com' });
await client.sessions.wait(session.id, {
condition: { kind: 'time', ms: 1000 },
});
const png = await client.sessions.capture(session.id, { kind: 'screenshot' });
console.log('Captured', png.byte_size, 'bytes');
// Clean up — destroy is idempotent.
await client.sessions.destroy(session.id); For long-running batch workloads, prefer webhooks (see webhooks) so you don't pay for idle polling.
5. Paginate over historical sessions
for await (const s of client.sessions.iterate({ limit: 50 })) {
if (s.status === 'destroyed') console.log(s.id);
} iterate walks cursor pages transparently and
stops when the server returns
next_cursor: null. The single-page form is
client.sessions.list({ limit, cursor }).
Error handling
Every SDK method throws a typed DriftstackError on
non-2xx responses. The error carries kind
(discriminator, narrow with ===),
status, type (the RFC 7807 URI),
detail, and any extension fields from the problem
response. Switch on kind for clean type-narrowing:
import { DriftstackError } from '@driftstack/sdk';
try {
await client.sessions.create({ archetype: 'definitely-not-a-real-archetype' as never });
} catch (err) {
if (err instanceof DriftstackError && err.kind === 'validation') {
// ValidationError subclass — `err.issues` lists per-field problems
} else if (err instanceof DriftstackError && err.kind === 'rate_limited') {
// RateLimitedError subclass — `err.retryAfterSeconds` is set
} else {
throw err;
}
} Bundled vs tree-shaken
The SDK is fully tree-shakeable. If you only import
client.sessions.*, the recordings and billing
modules never reach your bundle. Typical Next.js / Vite
production bundles add ~12 kB gzipped.
Where to go next
- Full API reference — every endpoint, every field.
- SDK — crypto orders — typed examples for the surface.
- Webhooks — avoid polling in production.
- Cost monitoring — set alerts before the bill surprises you.
- Error codes — the stable RFC 7807 type URIs the SDK's typed errors carry.
Need help?
[email protected]. We respond within one business day.