@agflowai/sdk
v1.0.0
Published
Official JS/Node SDK for AgentFlow
Readme
@agflowai/sdk
Official Node.js SDK for AgentFlow — programmatic access to workspaces, runs, streaming events, and artifacts.
- Node.js ≥ 18 required (uses native
fetchandReadableStream) - Zero runtime dependencies
- Full TypeScript declarations included
Installation
npm install @agflowai/sdkQuick Start
const { AgentFlow } = require("@agflowai/sdk");
const af = new AgentFlow({
baseUrl: "https://api.yourdomain.com",
apiKey: "af_live_...",
orgId: "00000000-0000-0000-0000-000000000000",
});
const { run } = await af.runs.create({
workspace_id: "wks_...",
input_text: "Analyse Q1 results",
});
for await (const event of af.runs.events(run.id)) {
console.log(event.type, event.data);
}Authentication
Generate an API key from Settings → API Keys in the AgentFlow dashboard. Keys take the form:
| Prefix | Usage |
| ------------- | ---------------------- |
| af_live_... | Production / live data |
| af_test_... | Test / sandbox data |
Every request carries Authorization: Bearer <apiKey> and X-Org-Id: <orgId>.
Constructor
new AgentFlow(options);| Option | Type | Required | Default | Description |
| --------- | -------- | -------- | ------- | -------------------------------------------------- |
| baseUrl | string | ✅ | — | API base URL, no trailing slash |
| apiKey | string | ✅ | — | API key (af_live_ or af_test_) |
| orgId | string | ✅ | — | UUID of the organisation to operate in |
| timeout | number | | 30000 | Request timeout in ms (not applied to SSE streams) |
| retries | number | | 2 | Retry count on transient 5xx GET errors |
Resources
After instantiation the SDK exposes three resource namespaces:
af.templates — workflow templates
af.workspaces — workspaces
af.runs — runs, events, artifactsaf.templates
templates.list()
List all templates visible to the org (platform + org-owned).
const { templates } = await af.templates.list();Returns: { templates: Template[] }
templates.get(slug)
Get a single template by its slug.
const { template } = await af.templates.get("investment-analyst-desk");Returns: { template: Template }
Throws: AgentFlowError(404) if not found.
templates.update(slug, body)
Update a template (org admin only). Sends a PATCH — include only fields you want to change.
const { template } = await af.templates.update("investment-analyst-desk", {
description: "Updated description",
});| Field | Type | Description |
| ------------------------ | -------- | --------------------------------------- |
| name | string | Display name |
| description | string | Short description |
| category | string | Template category |
| definition_json | object | Full template definition |
| definition_schema_json | object | JSON schema for the template |
| contract_json | object | Contract configuration for the template |
Returns: { template: Template }
af.workspaces
workspaces.list(opts?)
List workspaces in the org.
const { workspaces, total } = await af.workspaces.list({
search: "billing", // optional workspace name filter
template_search: "support", // optional template name filter
limit: 20,
offset: 0,
});| Option | Type | Default | Description |
| ----------------- | -------- | ------- | ------------------------ |
| search | string | — | Filter by workspace name |
| template_search | string | — | Filter by template name |
| limit | number | 10 | Max results (1–200) |
| offset | number | 0 | Pagination offset |
Returns: { workspaces: Workspace[], total: number, limit: number, offset: number }
workspaces.get(id)
Get a single workspace by UUID.
const { workspace } = await af.workspaces.get("wks_...");Returns: { workspace: Workspace }
workspaces.create(body)
Create a new workspace. Accepts either template_id (UUID) or template_slug (string).
// Using a slug (SDK resolves it automatically)
const { workspace } = await af.workspaces.create({
name: "Q1 Analysis",
template_slug: "investment-analyst-desk",
});
// Using a UUID
const { workspace } = await af.workspaces.create({
name: "Q1 Analysis",
template_id: "tpl_...",
});| Field | Type | Required | Description |
| --------------- | -------- | -------- | -------------------------------------------- |
| name | string | ✅ | Workspace display name |
| template_id | string | ✅* | Template UUID |
| template_slug | string | ✅* | Template slug (alternative to template_id) |
* One of template_id or template_slug must be provided.
Returns: { workspace: Workspace }
workspaces.update(id, body)
Update a workspace (PATCH). Include only fields to change.
const { workspace } = await af.workspaces.update("wks_...", {
name: "Revised Q1 Analysis",
});Returns: { workspace: Workspace }
af.runs
runs.create(body)
Create and dispatch a new run.
const { run } = await af.runs.create({
workspace_id: "wks_...",
input_text: "Analyse the impact of AI on software jobs in 2026.",
});| Field | Type | Required | Description |
| ------------------ | -------- | -------- | ------------------------------------------------------ |
| workspace_id | string | ✅ | Workspace UUID |
| input_text | string | | Free-text prompt |
| input_json | object | | Structured input (validated against template contract) |
| provider_id | string | | Override workspace default provider |
| title | string | | Run title (max 160 chars) |
| replay_of_run_id | string | | Provenance reference |
Returns: { run: Run, runtime: object }
runs.get(id)
Get a run by UUID, including tasks, artifacts, and template metadata.
const { run, tasks, artifacts, template } = await af.runs.get("run_...");Returns: { run: Run, tasks: Task[], artifacts: Artifact[], template: Template | null }
runs.list(opts?)
List runs in the org.
const { runs, total } = await af.runs.list({
workspace_id: "wks_...",
status: "done",
sort: "created_at",
dir: "desc",
limit: 20,
});| Option | Type | Default | Description |
| -------------- | -------- | ------------ | -------------------------------------------------- |
| workspace_id | string | — | Filter by workspace |
| runner_id | string | — | Filter by runner |
| status | string | — | done | failed | running | planning | … |
| search | string | — | Substring match on title or input_text |
| sort | string | created_at | Sort field |
| dir | string | desc | asc | desc |
| limit | number | 10 | Max results (1–200) |
| offset | number | 0 | Pagination offset |
Returns: { runs: Run[], total: number, limit: number, offset: number }
runs.cancel(id)
Cancel an active run.
await af.runs.cancel("run_...");Returns: { ok: true }
Throws: AgentFlowError(409) if the run is already in a terminal state.
runs.rerun(id, opts?)
Rerun a completed run using the same or overridden input.
const run = await af.runs.rerun("run_...", {
mode: "snapshot", // 'snapshot' | 'latest' (default: 'snapshot')
input_override: {
// shallow-merged onto source run's input_json
ticker: "NVDA",
},
});| Option | Type | Default | Description |
| ---------------- | -------- | ---------- | --------------------------------------------------------------------------- |
| mode | string | snapshot | snapshot reuses the source run's input (both modes use the current template in v1); latest is reserved for future template-version pinning |
| input_override | object | — | Partial input to override on the source run's input_json |
Returns: Run (the newly created run)
Throws: AgentFlowError(409) if the source run is still active.
runs.events(runId, opts?)
Stream live events from a run as an async iterable. Automatically terminates when a terminal event (run.finished, run.failed, run.cancelled) is received.
for await (const event of af.runs.events("run_...")) {
console.log(event.type, event.data);
}With abort support:
const controller = new AbortController();
setTimeout(() => controller.abort(), 60_000); // 60-second timeout
for await (const event of af.runs.events("run_...", {
signal: controller.signal,
})) {
console.log(event.type, event.data);
}Each yielded event:
{
id: 'evt_...', // SSE event ID (or null)
type: 'task.done', // event type string
data: { ... } // parsed JSON payload (or raw string if not JSON)
}Common event types:
| Type | Description |
| --------------- | ---------------------------------------------- |
| run.started | Run has been accepted and is starting |
| run.planning | Plan is being generated |
| task.started | A task has started executing |
| task.stream | Incremental LLM output chunk for a task |
| task.done | A task completed successfully |
| task.failed | A task failed |
| run.finished | All tasks complete — terminal, stream ends |
| run.failed | Run failed — terminal, stream ends |
| run.cancelled | Run was cancelled — terminal, stream ends |
| Option | Type | Default | Description |
| ------------- | ------------- | ------- | ---------------------------------------- |
| history | boolean | true | Replay persisted events before live tail |
| signal | AbortSignal | — | Cancellation signal |
| lastEventId | string | — | Resume SSE stream from this event ID |
runs.downloadArtifact(runId, artifactId, opts?)
Download a run artifact as a Buffer.
const { buffer, filename, contentType } = await af.runs.downloadArtifact(
"run_...",
"art_...",
);
fs.writeFileSync(filename ?? "output.bin", buffer);| Option | Type | Description |
| -------- | ------------- | ------------------- |
| signal | AbortSignal | Cancellation signal |
Returns: { buffer: Buffer, filename: string | null, contentType: string | null }
Error Handling
All errors thrown by the SDK are instances of AgentFlowError (or its subclass AgentFlowQuotaError).
const {
AgentFlow,
AgentFlowError,
AgentFlowQuotaError,
} = require("@agflowai/sdk");
try {
const { run } = await af.runs.create({ workspace_id: "wks_..." });
} catch (err) {
if (err instanceof AgentFlowQuotaError) {
console.error(`Quota exceeded: ${err.code}`); // e.g. QUOTA_RUNS_MONTHLY
console.error(`Limit: ${err.limit}`);
console.error(`Upgrade: ${err.upgradeUrl}`);
} else if (err instanceof AgentFlowError) {
console.error(`API error ${err.status}: ${err.message}`); // e.g. 404 Not found
console.error(`Code: ${err.code}`); // optional machine-readable code
} else {
throw err; // network error etc.
}
}AgentFlowError
| Property | Type | Description |
| --------- | --------------------- | ------------------------------------ |
| message | string | Human-readable error message |
| status | number | HTTP status code |
| code | string \| undefined | Machine-readable error code from API |
AgentFlowQuotaError extends AgentFlowError
Thrown on 429 responses with a QUOTA_* code.
| Property | Type | Description |
| ------------ | --------------------- | --------------------------------- |
| limit | number | The quota limit that was exceeded |
| upgradeUrl | string \| undefined | URL to the upgrade/billing page |
TypeScript
TypeScript declarations are bundled. No @types/ package needed.
import { AgentFlow, AgentFlowError, AgentFlowQuotaError } from '@agflowai/sdk';
import type { Run, Task, Artifact, RunEvent } from '@agflowai/sdk';
const af = new AgentFlow({
baseUrl: process.env.AGFLOW_API_URL!,
apiKey: process.env.AGFLOW_API_KEY!,
orgId: process.env.AGFLOW_ORG_ID!,
});
const { run }: { run: Run } = await af.runs.create({
workspace_id: 'wks_...',
input_text: 'Hello',
});
for await (const event: RunEvent of af.runs.events(run.id)) {
console.log(event.type);
}Examples
All examples live in examples/. Each file is self-contained and runnable with node. Set the environment variables described in each file's header before running.
Common setup
Every example reads these env vars:
export AGFLOW_API_URL=https://api.yourdomain.com
export AGFLOW_API_KEY=af_live_...
export AGFLOW_ORG_ID=00000000-0000-0000-0000-000000000000start-run.js — Full end-to-end run
Use case: Kick off a single run from scratch and wait for it to finish.
Covers:
- Creating a workspace from a
template_slug - Dispatching a run with
input_text - Polling with
runs.get()until the run reaches a terminal status - Printing task results and downloading artifacts
- Displaying estimated cost
TEMPLATE_SLUG=investment-analyst-desk node examples/start-run.jsstream-events.js — Live event streaming
Use case: Watch a run's progress in real time, event by event.
Covers:
- Starting a new run or rerunning an existing one (set
RERUN_ID) - Consuming the SSE event stream with
for await ... of af.runs.events() - Graceful Ctrl-C cancellation via
AbortController - Handling
AbortErrorcleanly
WORKSPACE_ID=<uuid> node examples/stream-events.js
# or rerun an existing run:
RERUN_ID=<run-uuid> node examples/stream-events.jslist-runs.js — Run history and cost report
Use case: Audit or report on runs in a workspace — useful for dashboards, billing checks, or validating a batch.
Covers:
- Paginating through
runs.list()withlimit/offset - Filtering by
status(e.g.STATUS=done) - Printing a summary table with status, cost, and title
- Aggregating total cost and token usage across all runs
WORKSPACE_ID=<uuid> node examples/list-runs.js
# filter to only completed runs:
STATUS=done WORKSPACE_ID=<uuid> node examples/list-runs.jsbatch-runs.js — Parallel fan-out
Use case: Process a list of inputs concurrently — one run per input — and collect all results.
Common pattern for per-ticker analysis, per-client reports, or bulk document processing.
Covers:
- Dispatching N runs simultaneously with
Promise.allSettled - Polling all in-flight runs in parallel until every one settles
- Handling partial failures (some succeed, some fail)
- Reporting a final cost and pass/fail summary
# Edit the INPUTS array in the file to match your workload, then:
WORKSPACE_ID=<uuid> node examples/batch-runs.jsrerun-failed.js — Batch retry failed runs
Use case: After fixing a provider outage, bad config, or transient error — find every failed run in a workspace and requeue them all in one command.
Covers:
- Listing runs filtered to
status: 'failed' DRY_RUN=1mode to preview what would be rerun before committing- Fan-out of
runs.rerun()calls withPromise.allSettled - Reporting which reruns succeeded and which errored
# Preview what would be rerun (no mutations):
DRY_RUN=1 WORKSPACE_ID=<uuid> node examples/rerun-failed.js
# Execute:
WORKSPACE_ID=<uuid> node examples/rerun-failed.jsRunning Tests
cd packages/sdk
npm testUses the Node.js built-in test runner (node:test). No extra dependencies.
License
MIT
