> ## Documentation Index
> Fetch the complete documentation index at: https://smithers-feat-claude-workflow-mirror.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Gateway Client API

> The low-level typed RPC + WebSocket SDK for a Smithers Gateway.

This is the transport layer beneath the UI: a typed client that speaks the
Gateway's HTTP RPC and WebSocket protocol, plus the TanStack DB collection
factory that turns those calls into live, reconnecting data. It is
framework-free on purpose.

<Note>
  Most UIs should not touch this. Reach for the React bindings in
  [`/reference/gateway-react`](/reference/gateway-react), which hold one client
  for you and expose live hooks. Drop down to the client only for non-React hosts,
  custom transports, or scripts.
</Note>

Everything on this page lives at the subpath
`smithers-orchestrator/gateway-client`, **not** on the bare
`smithers-orchestrator` facade. Import from the subpath or the build fails.

```ts theme={null}
import {
  SmithersGatewayClient,
  SmithersGatewayConnection,
  GatewayRpcError,
  createSmithersGatewayTransport,
  gatewayBackoffDelay,
  createGatewayCollection,
  gatewayKeys,
} from "smithers-orchestrator/gateway-client";
```

## SmithersGatewayClient

The typed client. Construct it with a base URL (and optional auth token); call a
named RPC method, or open a WebSocket and subscribe to a run's event stream.

```ts theme={null}
const client = new SmithersGatewayClient({
  baseUrl: "http://127.0.0.1:7331",
  token: process.env.SMITHERS_TOKEN,
});
```

<ParamField path="options" type="SmithersGatewayClientOptions">
  <Expandable title="SmithersGatewayClientOptions">
    <ParamField path="baseUrl" type="string">
      Gateway origin. Defaults to `globalThis.location.origin` in a browser, else
      `http://127.0.0.1:7331`. A trailing slash is stripped; `ws+unix:` URLs are
      supported for local sockets.
    </ParamField>

    <ParamField path="token" type="string">
      Bearer token sent as the `authorization` header on RPC and in the WebSocket
      `connect` handshake.
    </ParamField>

    <ParamField path="headers" type="HeadersInit">
      Extra headers merged into every HTTP RPC request.
    </ParamField>

    <ParamField path="fetch" type="typeof fetch">
      `fetch` override. Defaults to `globalThis.fetch`; calls reject if neither is
      available.
    </ParamField>

    <ParamField path="WebSocket" type="typeof WebSocket">
      `WebSocket` override. Defaults to `globalThis.WebSocket`; `connect` throws if
      neither is available.
    </ParamField>

    <ParamField path="client" type="{ id?, version?, platform? }">
      Client identity sent in the `connect` handshake.
    </ParamField>
  </Expandable>
</ParamField>

### RPC methods

Each method is a thin typed wrapper over `rpc(method, params)`, which POSTs to
`/v1/rpc/<method>` and unwraps the response frame. Params and return shapes come
from the method catalog; see [`/rpc/launch-run`](/rpc/launch-run) for one method
in full.

```ts theme={null}
const { runId } = await client.launchRun({ workflowId: "review", input: {} });
await client.submitApproval({ runId, nodeId: NODE_ID, decision: "approve" });
const runs = await client.listRuns();
await client.cancelRun({ runId });
```

<ResponseField name="run lifecycle" type="methods">
  `launchRun`, `resumeRun`, `cancelRun`, `hijackRun`, `rewindRun`.
</ResponseField>

<ResponseField name="human-in-the-loop" type="methods">
  `submitApproval`, `submitSignal`, `listApprovals`.
</ResponseField>

<ResponseField name="reads" type="methods">
  `getRun`, `listRuns`, `listWorkflows`, `getNodeOutput`, `getNodeDiff`.
</ResponseField>

<ResponseField name="crons" type="methods">
  `cronList`, `cronCreate`, `cronDelete`, `cronRun`.
</ResponseField>

<ResponseField name="escape hatches" type="methods">
  `rpc(method, params, { signal? })` for any typed method, `rpcRaw(method,
      params?, { signal? })` for an untyped one, and `extensionRpc(namespace, key,
      params?)` / `streamExtension(namespace, key, params?)` for gateway extensions.
</ResponseField>

### Subscribing to events

`connect()` opens a `SmithersGatewayConnection`. The higher-level generators
filter and yield frames for you, so most callers iterate one of those instead of
driving the connection directly.

```ts theme={null}
const ac = new AbortController();
for await (const frame of client.streamRunEvents(
  { runId: RUN_ID },
  { signal: ac.signal },
)) {
  console.log(frame.event, frame.payload);
}
```

<ResponseField name="streamRunEvents(params, { signal? })" type="AsyncGenerator<GatewayEventFrame>">
  Subscribes to one run and yields its `run.*` frames (`run.event`,
  `run.gap_resync`, `run.heartbeat`, `run.error`). Pass `afterSeq` to resume.
  Closes the socket when iteration ends.
</ResponseField>

<ResponseField name="streamRunEventsResilient(params, options?)" type="AsyncGenerator<GatewayEventFrame>">
  Same frames, but reconnects with backoff + jitter on a silent drop and resumes
  from the last observed `seq`. Stops on terminal completion or abort. Options:
  `signal`, `backoff` ([`GatewayBackoffOptions`](#gatewaybackoffdelay)),
  `healthyAfterMs`, and `onReconnect(event)`.
</ResponseField>

<ResponseField name="streamDevTools(params, { signal? })" type="AsyncGenerator<GatewayEventFrame>">
  Subscribes to a run's DevTools snapshot stream.
</ResponseField>

<ResponseField name="connect({ subscribe?, signal? })" type="Promise<SmithersGatewayConnection>">
  Opens the WebSocket, performs the `connect` handshake (protocol + auth), and
  returns the live connection. Use this only when you need request/response over
  the socket or raw event frames.
</ResponseField>

### SmithersGatewayConnection

A single open WebSocket. RPC over the socket via `request` / `requestRaw`, and a
single in-order async stream of server event frames via `events(signal?)`. One
consumer per connection; the generators above own a connection each.

```ts theme={null}
const conn = await client.connect({ subscribe: [RUN_ID] });
const sub = await conn.request("streamRunEvents", { runId: RUN_ID });
for await (const frame of conn.events()) {
  if (frame.event === "run.event") console.log(frame.payload);
}
conn.close();
```

<ResponseField name="request(method, params)" type="Promise<payload>">
  Typed request/response over the socket.
</ResponseField>

<ResponseField name="requestRaw(method, params?)" type="Promise<unknown>">
  Untyped request/response over the socket.
</ResponseField>

<ResponseField name="events(signal?)" type="AsyncGenerator<GatewayEventFrame>">
  In-order server event frames. Ends on close; throws on an error or invalid
  frame.
</ResponseField>

<ResponseField name="close()" type="void">
  Closes the socket, rejects pending requests, and ends `events()`.
</ResponseField>

**Source** [`SmithersGatewayClient.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/SmithersGatewayClient.ts) · [`SmithersGatewayConnection.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/SmithersGatewayConnection.ts) · **Tests** [`SmithersGatewayClient.test.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/tests/SmithersGatewayClient.test.ts) · **See also** [`/rpc/launch-run`](/rpc/launch-run), [Gateway integration](/integrations/gateway)

## GatewayRpcError

The error every RPC rejects with on a failed frame or HTTP error. Inspect `code`
to branch (for example, `requiredScope` is set on an authorization failure).

```ts theme={null}
try {
  await client.launchRun({ workflowId: "review", input: {} });
} catch (error) {
  if (error instanceof GatewayRpcError) {
    console.error(error.method, error.code, error.requiredScope);
  }
}
```

<ResponseField name="method" type="string">
  The RPC method that failed (or `"websocket"` for a malformed socket frame).
</ResponseField>

<ResponseField name="code" type="string">
  Machine-readable error code, e.g. `HTTP_ERROR`, `INVALID_GATEWAY_RESPONSE`, or
  the gateway's own code from the response frame.
</ResponseField>

<ResponseField name="status" type="number">
  HTTP status when the failure came over HTTP RPC.
</ResponseField>

<ResponseField name="requiredScope" type="string">
  The scope the call needed, on an authorization failure.
</ResponseField>

<ResponseField name="refresh" type="string">
  Set when the gateway asks the client to refresh credentials.
</ResponseField>

<ResponseField name="details" type="unknown">
  Extra context attached by the gateway.
</ResponseField>

**Source** [`GatewayRpcError.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/GatewayRpcError.ts) · **Tests** [`gateway-client.test.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/tests/gateway-client.test.ts)

## createSmithersGatewayTransport

Adapts a `SmithersGatewayClient` into the narrow `SyncTransport` that
[`createGatewayCollection`](#collections) and the React provider consume. RPC is
wired straight through; `streamRunEvents` uses the resilient generator,
`streamDevTools` the plain one. Unknown stream scopes throw, so a typo surfaces
loudly instead of stalling.

```ts theme={null}
const transport = createSmithersGatewayTransport(client, {
  streamHealthyAfterMs: 1000,
});
```

<ParamField path="client" type="SmithersGatewayClient" required>
  The client whose RPC and streams the transport forwards.
</ParamField>

<ParamField path="options" type="CreateSmithersGatewayTransportOptions">
  <Expandable title="CreateSmithersGatewayTransportOptions">
    <ParamField path="streamHealthyAfterMs" type="number">
      `healthyAfterMs` passed to the run-event resilient generator before backoff
      resets.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="SyncTransport" type="object">
  `{ rpc(method, params, options?), stream(scope, params, options) }`. `scope` is
  `"streamRunEvents"` or `"streamDevTools"`; both require `params.runId`.
</ResponseField>

**Source** [`createSmithersGatewayTransport.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/sync/createSmithersGatewayTransport.ts) · **Tests** [`sync`](https://github.com/smithersai/smithers/tree/main/packages/gateway-client/tests/sync)

## gatewayBackoffDelay

Exponential backoff with full jitter for one 0-based attempt. The resilient
stream uses it internally; call it directly when you write your own reconnect
loop.

```ts theme={null}
const delayMs = gatewayBackoffDelay(attempt, { baseMs: 250, maxMs: 10_000 });
```

<ParamField path="attempt" type="number" required>
  0-based attempt index. The base delay grows by `factor ** attempt`, capped at
  `maxMs`.
</ParamField>

<ParamField path="options" type="GatewayBackoffOptions">
  <Expandable title="GatewayBackoffOptions">
    <ParamField path="baseMs" type="number" default="250">
      Base delay before growth and jitter.
    </ParamField>

    <ParamField path="maxMs" type="number" default="10000">
      Ceiling on the pre-jitter delay.
    </ParamField>

    <ParamField path="factor" type="number" default="2">
      Per-attempt growth multiplier.
    </ParamField>

    <ParamField path="jitter" type="number" default="0.5">
      Symmetric jitter fraction applied to the delay.
    </ParamField>

    <ParamField path="random" type="() => number" default="Math.random">
      RNG override for deterministic tests.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="number" type="number">
  Milliseconds to wait, never negative.
</ResponseField>

**Source** [`gatewayBackoffDelay.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/gatewayBackoffDelay.ts) · **Tests** [`gatewayBackoffDelay.test.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/tests/gatewayBackoffDelay.test.ts)

## Collections

`createGatewayCollection` builds a TanStack DB `CollectionConfig` backed by a
`SyncTransport`: it loads an initial RPC payload, then keeps the collection live
off a stream (or by refetching on each frame). `gatewayKeys` produces the stable
cache keys so two consumers of the same data never disagree on the key.

```ts theme={null}
import { createCollection } from "@tanstack/db";

const runs = createCollection(
  createGatewayCollection<GatewayRunRow>({
    key: gatewayKeys.runs(),
    client: transport,
    getKey: (row) => row.runId,
    method: "listRuns",
  }),
);
```

<ParamField path="config" type="GatewayCollectionConfig<TRow, TKey>" required>
  <Expandable title="GatewayCollectionConfig">
    <ParamField path="key" type="SyncKey" required>
      Cache key for the collection. Use a [`gatewayKeys`](#gatewaykeys) factory.
    </ParamField>

    <ParamField path="client" type="SyncTransport" required>
      Transport from [`createSmithersGatewayTransport`](#createsmithersgatewaytransport).
    </ParamField>

    <ParamField path="getKey" type="(row) => TKey" required>
      Extracts each row's primary key.
    </ParamField>

    <ParamField path="method" type="string">
      RPC method for the initial load (and for refetch-driven streams).
    </ParamField>

    <ParamField path="params" type="unknown">
      Params passed to `method`.
    </ParamField>

    <ParamField path="rows" type="(payload) => Iterable<TRow>">
      Maps an RPC payload to rows. Defaults to the payload itself (array or single
      object).
    </ParamField>

    <ParamField path="stream" type="object">
      Live-stream config: `scope`, `params`, `afterSeq`, one of
      `frameToWrites` / `frameToRows` / `refetchOnFrame`, plus bounds
      (`maxRows`, `maxBufferedFrames`), `reconnectOnGracefulEnd`, and `backoff`.
    </ParamField>

    <ParamField path="compare" type="(a, b) => number">
      Stable ordering for the collection.
    </ParamField>

    <ParamField path="onError / onAuthError" type="(error: Error) => void">
      Error callbacks; `onAuthError` fires on 401/403-class failures.
    </ParamField>

    <ParamField path="onInsert / onUpdate / onDelete" type="handler">
      Optimistic mutation handlers forwarded to TanStack DB.
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="CollectionConfig<TRow, TKey>" type="object">
  A config to hand to TanStack DB's `createCollection`. Its `id` is the
  fingerprint of `key`.
</ResponseField>

### gatewayKeys

Typed cache-key factories for the known gateway reads and streams. Each returns a
`readonly` `SyncKey` tuple.

```ts theme={null}
gatewayKeys.runs();                 // ["gateway:listRuns", {}]
gatewayKeys.run(RUN_ID);            // ["gateway:getRun", { runId }]
gatewayKeys.runEvents(RUN_ID);      // ["gateway:streamRunEvents", { runId }]
gatewayKeys.nodeOutput(RUN_ID, NODE_ID);
```

<ResponseField name="gatewayKeys" type="object">
  Factories include `workflows`, `runs`, `run`, `approvals`, `nodeOutput`,
  `nodeDiff`, `cronList`, `memoryFacts`, `prompts`, `scores`, `tickets`,
  `devtoolsSnapshot`, `runEvents`, and `devtools`.
</ResponseField>

**Source** [`createGatewayCollection.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/sync/createGatewayCollection.ts) · [`gatewayKeys.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/sync/gatewayKeys.ts) · **Tests** [`sync`](https://github.com/smithersai/smithers/tree/main/packages/gateway-client/tests/sync)

## Sync internals

<Note>
  The subpath also exports lower-level building blocks used to assemble the
  collections above: `electricCollectionDefs` / `gatewayCollectionDefs` (the
  registry of known collections), `flattenGatewayRunNode` /
  `snapshotToGatewayRunNode` / `reconcileSnapshotNodes` (run-tree shaping),
  `syncKeyFingerprint` / `syncKeyMatches` (key hashing + prefix match),
  `syncBackoffDelay`, the Electric source (`createElectricCollection`), and the row
  types (`GatewayRunRow`, `GatewayRunNode`, `GatewayApprovalRow`, …). They are
  implementation surface for custom transports and rarely needed directly. See the
  [package source](https://github.com/smithersai/smithers/tree/main/packages/gateway-client/src/sync).
</Note>

***

**Source** [`index.ts`](https://github.com/smithersai/smithers/blob/main/packages/gateway-client/src/index.ts) · **Tests** [`packages/gateway-client/tests`](https://github.com/smithersai/smithers/tree/main/packages/gateway-client/tests) · **See also** [Gateway React API](/reference/gateway-react), [Gateway integration](/integrations/gateway), [`/rpc/launch-run`](/rpc/launch-run)
