TypeScript SDK

@flowkoi/sdk — types, async iterators, WS helper, webhook verification.

The official TypeScript SDK for FlowKoi. Works in Node 18+, modern browsers, Bun, Deno, and Cloudflare Workers.

npm install @flowkoi/sdk
# or pnpm add / yarn add

Source: github.com/FlowKoi/platform/tree/main/packages/sdk

Hello world

import { FlowKoi } from "@flowkoi/sdk";

const flowkoi = new FlowKoi({ apiKey: process.env.FLOWKOI_API_KEY! });

const run = await flowkoi.flows.run("flow_xxx", {
  prompt: "Summarise the latest README change.",
});

for await (const event of flowkoi.runs.stream(run.runId)) {
  if (event.type === "output") process.stdout.write(event.data);
  if (event.type === "finished") break;
}

Construction

new FlowKoi({
  apiKey: string;        // required
  baseUrl?: string;      // default 'https://api.flowkoi.com'
  fetch?: typeof fetch;  // for tests / Workers / custom proxies
});

The constructor only validates apiKey is present — no network round-trip — so it’s cheap to new FlowKoi() per request if you prefer.

Surface

Flows

flowkoi.flows.list();                          // Flow[]
flowkoi.flows.run(flowId, {
  prompt?: string,
  callbackUrl?: string,
  idempotencyKey?: string,
});                                            // Run

Runs

flowkoi.runs.get(runId);                       // Run
flowkoi.runs.logs(runId, { offset?, limit? }); // RunLogsSlice
flowkoi.runs.stop(runId);                      // { runId, stopping?: true, alreadyStopped?: true }
flowkoi.runs.delete(runId);                    // void

// Async-iterable SSE stream
for await (const ev of flowkoi.runs.stream(runId)) { /* ... */ }

Files & messages

flowkoi.runs.files.list(runId);                // FileEntry[]
flowkoi.runs.files.get(runId, path);           // RunFileContent
flowkoi.runs.files.inject(runId, [
  { path: "input.txt", content: "hi" },
]);                                            // { runId, persisted, liveDelivered }

flowkoi.runs.messages.send(runId, "now run the tests"); // { runId, delivered: true }

The SDK appends \n to messages so the agent’s PTY actually submits them — don’t add it yourself.

Streaming

The stream is an async iterable of typed events:

type StreamEvent =
  | { type: "output";        data: string }
  | { type: "status";        status: "running" | "sleep"; claudeStatus: ClaudeStatus | null }
  | { type: "claude_status"; status: "working" | "idle" | "waiting" | "error" }
  | { type: "finished";      sessionId: string; reason?: string; exitCode?: number }
  | { type: "filetree";      tree: unknown };

Behaviour worth knowing:

  • Stream auto-closes on finished.
  • Breaking out of the for await early cancels the underlying fetch — no leaked sockets.
  • : ping SSE heartbeats are silently dropped.
  • Late subscribers get the full persisted log as a single output event on join, then live frames.

Bidirectional WebSocket

import { connect } from "@flowkoi/sdk/ws";

const sock = connect(flowkoi, runId);
sock.on<string>("terminal:output", (data) => process.stdout.write(data));
sock.sendMessage("write a haiku about the API");
sock.injectFiles([{ path: "README.md", content: "..." }]);
sock.close();

sock.raw exposes the underlying WebSocket if you need it.

Polyfilling on old Node

Node 22+ has globalThis.WebSocket. On older Node:

import WebSocket from "ws";
connect(flowkoi, runId, { WebSocket });

Webhook verification

import { verifyWebhook } from "@flowkoi/sdk/webhook";

app.post("/webhooks/flowkoi", express.raw({ type: "application/json" }), async (req, res) => {
  const result = await verifyWebhook(
    req.body,
    req.header("x-flowkoi-signature"),
    process.env.FLOWKOI_WEBHOOK_SECRET!,
  );
  if (!result.valid) return res.status(401).end();
  await handleCompletion(result.payload);     // typed CompletionPayload
  res.status(200).end();
});

verifyWebhook is cross-runtime — uses WebCrypto when available (browser, Workers, Deno, Bun, modern Node), falls back to node:crypto dynamic import on older Node. The fallback is tree-shaken out of browser bundles automatically.

Pass the raw body — see Webhooks for why.

Errors

Every typed error subclass:

import {
  FlowKoiError,
  FlowKoiAuthError,
  FlowKoiNotFoundError,
  FlowKoiValidationError,
  FlowKoiConflictError,
  FlowKoiUpstreamError,
  FlowKoiRateLimitError,
  FlowKoiNetworkError,
} from "@flowkoi/sdk";

FlowKoiRateLimitError carries retryAfterSeconds, limit, current. The base FlowKoiError carries .code, .status, .body so you can introspect codes the SDK hasn’t given a dedicated class to yet.

Versioning

The SDK follows semver. 0.x while the surface settles; breaking changes get a release note. The SDK pins to a major version of the public API (/api/v1/*).