Skip to main content

Example projects

Recipes for the integrations people build first. Each one is a complete, working shape — the essential code is inline here, ready to adapt.

Every hosted-API recipe needs two things from Workbench: a flow's Flow ID (dev drawer → Quickstart) and an API key with the workbench:flows:run scope. Keys are secrets — they belong in server-side environment variables, never in browser code.

Run a flow from Node.js

Zero dependencies. Chat-shaped input for agent flows; swap kind: "form" with values keyed by your input nodes for data flows. (Full reference)

const res = await fetch(
  `https://api.zerowidth.ai/1.0/flows/${process.env.FLOW_UUID}/runs`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ZEROWIDTH_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      input: { kind: "chat", messages: [{ role: "user", content: "Hello!" }] },
      source: { kind: "published", version: "1.0.0" }, // pin for production
    }),
  },
)
const { status, outputs, costSummary } = await res.json()

Run a flow from Python

import os, requests

res = requests.post(
    f"https://api.zerowidth.ai/1.0/flows/{os.environ['FLOW_UUID']}/runs",
    headers={"Authorization": f"Bearer {os.environ['ZEROWIDTH_API_KEY']}"},
    json={"input": {"kind": "chat", "messages": [{"role": "user", "content": "Hello!"}]}},
    timeout=120,
)
result = res.json()
print(result["outputs"])

A streaming chat app

Two pieces: a tiny server-side proxy that adds the API key and forwards the streamed events (so the key never reaches the browser), and a client that appends node_update token deltas as they arrive. The streaming guide covers the event protocol; the client side reduces to:

// POST { messages } to your proxy, which forwards to the run API
// with stream: true — then read the streamed frames:
if (type === "node_update") appendToUi(data.data?.content ?? "")
if (type === "run_complete") reconcile(data.outputs)

Resend the full message history each turn — that's the shape the Agent pattern's chat input expects.

Batch structured extraction

Point a Structurer flow at a whole CSV: a bounded worker pool posts one form-shaped run per row and writes JSONL — failures are captured per-row instead of stopping the batch.

const result = await runFlow({ kind: "form", values: { data: row.text } })
out.push(result.status === "error" ? { row: i, error: result.message } : { row: i, outputs: result.outputs })

Pin a published version so a mid-batch edit can't change the extraction.

Call workspace tools over MCP, from code

The MCP page covers Claude Desktop and Cursor; the same server works programmatically with the official MCP SDK:

import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"

const client = new Client({ name: "my-app", version: "1.0.0" })
await client.connect(
  new StreamableHTTPClientTransport(new URL("https://api.zerowidth.ai/mcp"), {
    requestInit: { headers: { Authorization: `Bearer ${process.env.ZEROWIDTH_PAT}` } },
  }),
)
const { tools } = await client.listTools()

Run flows in your own infrastructure

The SDK executes exported flows in your own Node.js process — same engine as the hosted API. Two shapes worth knowing:

Plain embed — your process, your provider keys:

import Workbench from "@zerowidth/workbench-sdk"

const engine = await Workbench.create(flow, {
  keys: { openrouter: process.env.OPENROUTER_API_KEY },
})
const result = await engine.run({ query: "..." })

Bring your own database as a knowledge base — for corpora beyond a managed knowledge base's scale, implement the SDK's KnowledgeBaseInterface over your own store and inject it. Every knowledge node in the flow reads from your database; the flow itself doesn't change:

import Workbench, { KnowledgeBaseInterface } from "@zerowidth/workbench-sdk"

class MySqlKnowledge extends KnowledgeBaseInterface {
  async keywordSearch(query, { limit = 10 } = {}) {
    const rows = await myDb.query(
      "SELECT id, title, body FROM articles WHERE body ILIKE $1 LIMIT $2",
      [`%${query}%`, limit],
    )
    return rows.map((r, i) => ({
      id: r.id, document_id: r.id, document_name: r.title,
      chunk_index: i, content: r.body,
    }))
  }
  async disconnect() { await myDb.end() }
}

const engine = await Workbench.create(flow, {
  knowledgeBase: { instance: new MySqlKnowledge() },
})

Implement only the methods your flow's nodes use — semanticSearch against your vector store, query for the SQL node, listDocuments for browsing. Unimplemented methods return empty results, so flows degrade gracefully.

2 min read