Skip to main content

SDK reference

@orchestate/sdk is the official TypeScript client for Orchestate. It wraps the HTTP API with a small, typed surface for publishing machine definitions, creating instances, sending events, and subscribing to live state. Every method maps to a single REST call (or, for subscribe, a WebSocket), so the SDK stays a thin, predictable layer over the wire protocol.

Installation

Install the package from npm:

npm install @orchestate/sdk

The client uses the global fetch and WebSocket APIs. It runs in modern Node.js (18+), Cloudflare Workers, Deno, and the browser without a polyfill.

Initialization

Create a client by passing your API key. The class is named OrchestateClient:

import { OrchestateClient } from '@orchestate/sdk';

const client = new OrchestateClient({
  apiKey: process.env.ORCHESTATE_API_KEY!, // orch_<48 hex chars>
});

API keys are created in the console under Settings → API Keys and are sent on every request as Authorization: Bearer orch_…. See Auth & API keys for the full key lifecycle.

The constructor accepts an optional baseUrl. It defaults to https://orchestate.wentzel.ai/api — the managed service. Override it only when you run the Orchestate runtime in your own Cloudflare account or air-gapped workerd:

const client = new OrchestateClient({
  apiKey: process.env.ORCHESTATE_API_KEY!,
  baseUrl: 'https://orchestate.example.com/api', // self-hosted origin
});

Note — baseUrl should point at the /api path on your app origin, with no trailing slash. See Self-hosting for deploying the runtime.

Methods at a glance

Every method below maps to a single endpoint on the HTTP API:

MethodEndpointReturns
machines.publish(input)POST /api/machines{ id, version }
machines.list()GET /api/machinesPublishedMachine[]
machines.get(idOrSlug)GET /api/machines/{idOrSlug}PublishedMachine
instances.create(input)POST /api/instancesInstance
instances.list(params?)GET /api/instancesInstance[]
instances.send(id, event)POST /api/instances/{id}/eventsTransitionResult
instances.get(id)GET /api/instances/{id}Instance
instances.subscribe(id, cb)GET /api/inspect/{instanceId} (WebSocket)unsubscribe () => void

See HTTP API for the raw request/response contract behind each method.

Machines

client.machines.publish(input)

Publishes a machine definition. If the slug already exists, a new version is created. The slug must match ^[a-z0-9-]+$ and be 80 characters or fewer.

const machine = await client.machines.publish({
  slug: 'order',
  name: 'Order lifecycle',
  definition: {
    initial: 'pending',
    states: {
      pending: { on: { PAY: 'paid', CANCEL: 'cancelled' } },
      paid: { on: { SHIP: 'shipped' } },
      shipped: { type: 'final' },
      cancelled: { type: 'final' },
    },
  },
});

// machine => { id, version }

The definition follows the machine definition schema. Returns { id, version } where version increments on each republish of the same slug.

client.machines.list()

Lists every published machine in your organization.

const machines = await client.machines.list();

for (const m of machines) {
  console.log(m.slug, m.version);
}

// machines => PublishedMachine[]  -> [{ id, slug, version }, ...]

client.machines.get(idOrSlug)

Fetches a single machine by its id or slug, scoped to your organization.

const machine = await client.machines.get('order');

// machine => { id, slug, name, currentVersion, definition, createdAt, updatedAt }

Throws if no machine with that id or slug exists in your organization.

Instances

client.instances.create(input)

Creates a running instance of a machine. The optional context seeds the instance's context object.

const instance = await client.instances.create({
  machineSlug: 'order',
  context: { orderId: 'ord_123', total: 4200 },
});

// instance => { id, currentState, status, machineVersion, createdAt }
// currentState is the machine's initial state; status is 'active'

client.instances.list(params?)

Lists instances, newest first. Both filters are optional; pass machineSlug and/or status to narrow the result.

const active = await client.instances.list({
  machineSlug: 'order',
  status: 'active',
});

// active => Instance[]

The status filter accepts one of active, done, error, or cancelled.

client.instances.send(instanceId, event)

Sends an event to an active instance, driving a transition. The event type must match an entry in the current state's on map.

const result = await client.instances.send(instance.id, {
  type: 'PAY',
  payload: { method: 'card' },
});

// result => { fromState, toState, final }
// e.g. { fromState: 'pending', toState: 'paid', final: false }

if (result.final) {
  console.log('Instance reached a final state — status is now "done".');
}

Note — sending an event to an instance that is not active returns a 409, which the SDK surfaces as a thrown Error. Reaching a final state moves the instance status to done. A transition whose guard evaluates false is rejected with 422 and code: "GUARD_REJECTED".

Pass an optional idempotencyKey to make retries safe — a replayed key returns the original result (flagged replayed: true) without re-running the transition:

// derive the key from your domain entity so retries reuse it
const key = `submit-${order.id}`;

await client.instances.send(instance.id, { type: 'SUBMIT' }, { idempotencyKey: key });

client.instances.get(instanceId)

Fetches a single instance by id, scoped to your organization. Returns the current state and context snapshot persisted in the database.

const instance = await client.instances.get('ins_5a6b7c8d');

// instance => { id, currentState, context, status, machineVersion, createdAt, updatedAt }

For a continuously-updating view rather than a one-shot read, use instances.subscribe() below.

client.instances.history(instanceId, params?)

Returns the instance's transition log, newest first — the audit trail of every event the instance has processed. Paginate with limit (default 50, max 200) and before: the returned array also carries nextCursor, an opaque (createdAt, id) cursor to pass as before for the next page (null on the last page). Same-millisecond events never skip or repeat.

const events = await client.instances.history(instance.id, { limit: 20 });

// events => [{ id, eventType, payload, fromState, toState, createdAt }, ...]
// events.nextCursor => 'gc1.…' | null

const older = await client.instances.history(instance.id, {
  limit: 20,
  before: events.nextCursor,
});

Live updates

client.instances.subscribe(instanceId, callback) opens a WebSocket to the live inspector and invokes your callback on every transition. It returns an unsubscribe function that closes the connection.

const unsubscribe = client.instances.subscribe(instance.id, (state) => {
  // state => { value, context }
  console.log('now in', state.value, 'with', state.context);
});

// later, when you're done:
unsubscribe();

Note — the inspector WebSocket accepts either your API key (the SDK appends it to the socket URL automatically) or a logged-in browser session cookie, so subscribe() works from backends and dashboards alike. The callback receives { value, context }, where value is the new state name.

Types

The package exports the following types: MachineDefinition, Instance, and TransitionResult.

import type {
  MachineDefinition,
  Instance,
  TransitionResult,
} from '@orchestate/sdk';

// MachineDefinition — the JSON you publish:
//   { initial: string; states: Record<string, unknown>; context?: object }

// Instance — a running machine instance:
//   { id: string; currentState: string; context: object; status: string }

// TransitionResult — the outcome of instances.send():
//   { fromState: string; toState: string; final: boolean }

For the full MachineDefinition shape — states, transitions, and actions — see Machine definitions.


See also

  • HTTP API — the raw endpoints behind every SDK method.
  • Auth & API keys — issuing and revoking the keys the client uses.