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/sdkThe 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:
| Method | Endpoint | Returns |
|---|---|---|
machines.publish(input) | POST /api/machines | { id, version } |
machines.list() | GET /api/machines | PublishedMachine[] |
machines.get(idOrSlug) | GET /api/machines/{idOrSlug} | PublishedMachine |
instances.create(input) | POST /api/instances | Instance |
instances.list(params?) | GET /api/instances | Instance[] |
instances.send(id, event) | POST /api/instances/{id}/events | TransitionResult |
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.