Skip to main content
Driftstack DRIFTSTACK

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 (creatingreadybusydestroyed / 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

Need help?

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