Execute Workflow
Trigger Zafeguard workflow runs, inspect stage results, stream live status, and manage components with the WorkflowClient SDK.
Execute Workflow
Use WorkflowClient to trigger, stream, and inspect workflow runs. You need a Workspace API key (ws_...) and the UUID of the workflow to trigger. Get both from Dashboard → Workspace Settings → API Keys.
Setup
import { WorkflowClient } from '@zafeguard/caller-sdk';
const workflow = new WorkflowClient({
apiKey: process.env.ZAFEGUARD_API_KEY!, // ws_...
apiSecret: process.env.ZAFEGUARD_API_SECRET!, // Ed25519 secret issued with the key
workflowId: process.env.ZAFEGUARD_WORKFLOW_ID!, // UUID of your workflow
});Trigger a run
// `trigger()` returns an array — one entry per ON_API_CALLED component on
// the workflow. Workflows usually have exactly one, so destructure [0].
const [{ runId, jobId }] = await workflow.trigger();
console.log('Run started:', runId);Returns immediately with an array of { runId, jobId } — one entry per ON_API_CALLED component. The workflow starts executing asynchronously. If the workflow declares multiple ON_API_CALLED triggers, each starts its own run.
Passing input to ON_API_CALLED
If the workflow's ON_API_CALLED trigger declares dynamic input variables, pass them as an object — keys must match the variable names, values must match the declared dataType. The server forwards the object verbatim as the trigger's input output port.
const [{ runId }] = await workflow.trigger({
userId: 'u_123',
amount: '100',
metadata: { source: 'web' },
});Omit the argument when the trigger has no inputs (or you want them empty) — that's the legacy no-arg form and remains supported.
Wait for completion
The simplest way to get the final result — opens an SSE stream and resolves once the run finishes:
const run = await workflow.waitForRun(runId);
if (run.status === 'COMPLETED') {
console.log(`Done! Credits used: ${run.totalUsage}`);
for (const stage of run.runStages) {
console.log(` ${stage.module}: ${stage.status}`, stage.output);
}
} else if (run.status === 'FAILED') {
const failed = run.runStages.filter(s => s.status === 'FAILED');
console.error('Failed stages:', failed.map(s => s.module));
}Timeout
waitForRun has a configurable timeout (default 5 minutes):
const run = await workflow.waitForRun(runId, 120_000); // 2 minutesStream live progress
Use stream when you want per-stage updates as the workflow executes:
const sub = workflow.stream(runId, {
onUpdate(event) {
const pending = event.output.pendingStageCount ?? 0;
const total = event.output.totalUsage ?? 0;
console.log(`${event.status} | pending: ${pending} | credits: ${total}`);
// Currently active stages
for (const stage of event.output.nonTerminalStages ?? []) {
console.log(` → ${stage.module} (attempt ${stage.executionAttempt})`);
}
},
onError(err) {
console.error('Stream error:', err.message);
},
});
// Stream closes automatically on COMPLETED / FAILED.
// Cancellation surfaces as a FAILED run with `output.cancelRequestedAt` set.
// Call sub.close() to unsubscribe earlyGet run details
Fetch the full run state at any time, including all stage outputs:
const run = await workflow.execution.get(runId);
console.log(run.status); // 'COMPLETED'
console.log(run.pendingStageCount); // 0
console.log(run.failedStageCount); // 0
console.log(run.totalUsage); // credits used
for (const stage of run.runStages) {
console.log(stage.module, stage.status, stage.output);
}WorkflowRunDetail shape
interface WorkflowRunDetail {
id: string;
status: 'CREATED' | 'EXECUTING' | 'COMPLETED' | 'FAILED';
pendingStageCount: number;
failedStageCount: number;
// Cancellation surfaces as a FAILED run with cancelRequestedAt set —
// there is no separate CANCELED status.
cancelRequestedAt: string | null;
cancelReason: string | null;
totalUsage: number;
createdAt: string;
updatedAt: string;
runStages: Array<{
id: string;
module: string;
status: 'CREATED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
output: unknown | null;
error: unknown | null;
creditUsage: number;
executionAttempt: number; // 1 = first attempt
updatedAt: string;
}>;
}Update a workflow component
Modify a component's config, display name, or canvas position programmatically. Useful for dynamic workflows driven by external parameters:
const updated = await workflow.component.update('component-uuid', {
config: { apiUrl: 'https://new-api.example.com/endpoint' },
name: 'My API Call',
});
// updated: UpdateComponentResponse — { id, name, position, config, updatedAt }Returns the updated component snapshot (UpdateComponentResponse). You can update config, name, and position ([x, y]) independently:
// Move the component on the canvas
const updated = await workflow.component.update('component-uuid', {
position: [200, 400],
});Full example: trigger, stream, and inspect
import { WorkflowClient } from '@zafeguard/caller-sdk';
const workflow = new WorkflowClient({
apiKey: process.env.ZAFEGUARD_API_KEY!,
apiSecret: process.env.ZAFEGUARD_API_SECRET!,
workflowId: process.env.ZAFEGUARD_WORKFLOW_ID!,
});
async function runWorkflow(): Promise<void> {
// 1. Trigger (returns an array — one entry per ON_API_CALLED component)
const [{ runId }] = await workflow.trigger();
console.log('Started run:', runId);
// 2. Stream live updates
await new Promise<void>((resolve, reject) => {
const sub = workflow.stream(runId, {
onUpdate(event) {
const pending = event.output.pendingStageCount ?? 0;
console.log(` ${event.status} — ${pending} stages pending`);
if (event.status === 'COMPLETED') {
console.log(` Total credits: ${event.output.totalUsage}`);
resolve();
}
if (event.status === 'FAILED') {
reject(new Error(`Run failed — ${event.output.failedStageCount} stage(s) failed`));
}
},
onError: reject,
});
void sub; // sub.close() called automatically on terminal status
});
// 3. Inspect stage results
const run = await workflow.execution.get(runId);
for (const stage of run.runStages) {
console.log(` ${stage.module}: ${stage.status}`, stage.output);
}
}
runWorkflow().catch(console.error);Stage statuses
| Status | Meaning |
|---|---|
CREATED | Queued — waiting for upstream dependencies |
RUNNING | Actively executing |
COMPLETED | Finished successfully |
FAILED | Failed — see error field. Includes stages that were force-failed when their run was cancelled. |
A cancelled run finishes as FAILED with cancelRequestedAt and cancelReason set — there is no separate CANCELED status.
Error handling
import { WorkflowClient, CallerSDKError } from '@zafeguard/caller-sdk';
const workflow = new WorkflowClient({
apiKey: process.env.ZAFEGUARD_API_KEY!,
apiSecret: process.env.ZAFEGUARD_API_SECRET!,
workflowId: process.env.ZAFEGUARD_WORKFLOW_ID!,
});
try {
const [{ runId }] = await workflow.trigger();
const run = await workflow.waitForRun(runId);
} catch (err) {
if (err instanceof CallerSDKError) {
console.error(err.message); // e.g. "Invalid API key" or "Rate limit exceeded"
console.error(err.details); // API error body
}
if (err instanceof Error && err.message.includes('timed out')) {
console.error('Workflow took longer than the timeout');
}
}