Flow Run Trace API
List historical flow runs, fetch per-block input/output payloads, and live-tail an in-progress run over SSE.
Base URL: https://api.noukai.xyz/api/v1
When you execute a flow via /execute or /jobs, Noukai can persist each block's input and output payloads alongside the metadata it already tracks (status, duration, tokens, cost). The Flow Run Trace API exposes those records so you can:
- Audit historical runs and inspect what each block actually saw and produced.
- Drive a debugger UI that walks block-by-block through a completed run.
- Live-tail an in-progress run over Server-Sent Events.
Per-block payload capture is opt-in. With the default metadata_only capture mode, the trace endpoints return timing, token, and cost metadata but inputContext / outputContext come back null and only inputSizeBytes / outputSizeBytes are populated. Switch the flow or organization to full (or redacted) capture to receive payloads. See Capture Modes.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /flow-runs | List runs for a flow |
GET | /flow-runs/{flowRunId}/trace | Whole-run trace (latest attempt per step) |
GET | /flow-runs/{flowRunId}/steps/{stepId}/trace | Single-step trace, with attempt filter |
GET | /flow-runs/{flowRunId}/trace/stream | SSE live tail (replay + follow) |
Authentication
Same auth as the rest of the API — API keys or JWT tokens. The caller must have access to the project that owns the flow; otherwise 403.
List Runs
Returns the most recent runs for a flow, newest first.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
flow_id | string | Yes | Flow ID to list runs for. |
limit | integer | No | Max records to return. 1–100, default 20. |
status | string | No | Filter by run status: running, completed, failed, cancelled. |
cursor | string | No | Reserved for future pagination. Currently ignored. |
Response (200)
| Field | Type | Description |
|---|---|---|
id | string | Flow run ID. Use this in the trace fetches below. |
flowId | string | The flow this run belongs to. |
status | string | running, completed, failed, or cancelled. |
triggerType | string | null | How the run started. Free-form string set by the trigger source (e.g. api, test). Treat unknown values as opaque. |
startedAt | string | null | ISO 8601 timestamp. null until the run is dispatched. |
completedAt | string | null | ISO 8601 timestamp. null while running. |
durationMs | integer | null | End-to-end wall time. null while running. |
stepCount | integer | null | Number of step runs recorded for this flow run. |
Whole-Run Trace
Returns the flow run summary plus the latest attempt of every step. Retries collapse to just the most recent attempt; the only signal in this response that a step retried is attempt > 1. To see prior attempts, call the single-step endpoint with attempt=all.
Path Parameters
| Parameter | Description |
|---|---|
flowRunId | Flow run ID returned by List Runs or by /execute / /jobs. |
Response (200)
Step Trace Object
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier within the flow. Stable across attempts. |
attempt | integer | 1-based retry counter. |
status | string | running, completed, failed, or skipped. |
startedAt | string | null | ISO 8601. null for skipped steps. |
completedAt | string | null | ISO 8601. null while running. |
durationMs | integer | null | Wall time. null while running. |
modelUsed | string | null | Resolved model slug (e.g. openai/gpt-4o-mini) if the block invoked an LLM. |
tokens | object | null | { prompt, completion, total }. null for non-LLM blocks or steps that didn't consume tokens. |
costUsd | string | null | Cost in USD, serialized as a decimal string (e.g. "0.00041") to preserve precision. Parse on the client only when arithmetic is needed. null when the step wasn't metered. |
inputContext | object | null | The block's resolved input payload. null when capture mode is metadata_only / off. |
outputContext | object | null | The block's output payload. null for failed steps, in-flight steps, or metadata_only capture. |
errorContext | object | null | Present only on status: "failed". Shape: { "code": string, "message": string, "retryable": boolean }. |
inputSizeBytes | integer | null | Byte size of the input payload before any truncation. Populated even when inputContext is null. |
outputSizeBytes | integer | null | Byte size of the output payload. |
truncated | boolean | true if either payload exceeded the size cap and was truncated. The stored payload then carries a __truncated__: true marker at its root. |
Single-Step Trace
Fetch one step's trace, with control over which attempt(s) are returned.
Path Parameters
| Parameter | Description |
|---|---|
flowRunId | Flow run ID. |
stepId | Step identifier within the flow. |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
attempt | string | latest | latest returns the most recent attempt as a single object. all returns every attempt as a list. A numeric string (e.g. 1, 2) returns that specific attempt. |
Response — attempt=latest or a numeric value (200)
Returns a single Step Trace Object:
Response — attempt=all (200)
Each element of attempts is a Step Trace Object, ordered by attempt ascending. A step that did not retry returns a single-element list — not a 404. 404 is returned only when the step never ran at all.
Live-Tail Stream (SSE)
Subscribe to a flow run's trace as Server-Sent Events. The connection always opens with a replay of every event that has happened so far (reconstructed from the persisted trace), then transitions to a live tail of new events as the worker emits them. After replay, the stream stays open until the run finishes or the client disconnects.
Use this when:
- The run was just started and you want a UI to react event-by-event.
- The run started a moment ago and you want to catch up without polling.
- The run is already finished — replay alone gives you the full event log in one stream.
Replay-then-tail, no gap. The server subscribes to the live channel before it begins replay, so events emitted during the replay window are not lost. The server dedupes its own emissions on (stepId, attempt, eventName) so you will never receive the same step+attempt+event-name combination twice on a single connection.
Connection lifecycle
- Server emits
flow_started. - For each step that has progressed:
step_started, optionallystep_input, thenstep_outputorstep_error, thenstep_completed. (Events are omitted when the data for them is not yet — or never — available, e.g.step_inputis skipped undermetadata_onlycapture.) - If the flow run is already terminal at replay time, the server emits
flow_completedand closes the stream. - Otherwise the server holds the connection open and forwards events from the live channel as the run progresses, ending with
flow_completed.
Idle keepalive: the server emits an SSE comment frame (: ping\n\n) every 15 seconds with no activity to keep proxies from closing the connection. Clients should ignore frames starting with :.
Treat any disconnect as terminal. A clean termination is flow_completed followed by the server closing the connection. If the connection drops without flow_completed (network, timeout, server crash), do not assume the run is still progressing — re-open the stream to receive the full replay and learn the run's current state. The replay is idempotent: any events already shown will arrive again with the same (stepId, attempt, eventName) and your client-side dedup keeps the UI consistent.
Authentication failures return an HTTP error (401 / 403) before the stream opens — they are not delivered as SSE events.
Event Vocabulary
Every event carries a string event: header and a JSON data: payload. The same vocabulary is used by Noukai's test runner — a UI written against these events handles both production runs and editor test runs.
flow_started
| Field | Type | Description |
|---|---|---|
flowRunId | string | The run being streamed. |
flowId | string | The flow this run belongs to. |
startedAt | string | null | ISO 8601 timestamp. |
step_started
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier within the flow. |
attempt | integer | 1-based retry counter. |
startedAt | string | null | ISO 8601 timestamp. |
blockName | string | null | Human-readable name from the flow definition. May be omitted. |
step_input
Emitted only when capture mode is full or redacted. Omitted under metadata_only and off.
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier. |
attempt | integer | Retry counter. |
inputContext | object | null | Resolved input payload. May be null if the field was suppressed by redaction. |
inputSizeBytes | integer | Pre-truncation byte size. |
truncated | boolean | true if the payload was truncated. |
step_output
Emitted only when the step completed successfully and capture mode is full / redacted. Omitted under metadata_only and off.
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier. |
attempt | integer | Retry counter. |
outputContext | object | null | The block's output payload. |
outputSizeBytes | integer | Pre-truncation byte size. |
truncated | boolean | true if the payload was truncated. |
step_error
Emitted instead of step_output when the step failed.
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier. |
attempt | integer | Retry counter. |
errorContext | object | Stable fields: code (string), message (string), retryable (boolean). Additional fields may be present for debugging — treat them as opaque; the set is not part of the API contract. |
step_completed
Final event for a step. Fires after step_output (on success), after step_error (on failure), or alone when the step was skipped.
| Field | Type | Description |
|---|---|---|
stepId | string | Step identifier. |
attempt | integer | Retry counter. |
status | string | completed, failed, or skipped. |
durationMs | integer | Step wall time. 0 for skipped steps. |
tokens | object | null | { prompt, completion, total }. null for non-LLM blocks and skipped steps. |
costUsd | string | null | Decimal string (see precision note in Step Trace Object). null when not metered. |
modelUsed | string | null | Resolved model slug. null for non-LLM blocks. |
flow_completed
Last event in the stream. The server closes the connection after sending it.
| Field | Type | Description |
|---|---|---|
flowRunId | string | The run that just finished. |
status | string | completed, failed, or cancelled. |
durationMs | integer | End-to-end wall time. |
error | string | null | Step ID where the run failed, when status: "failed". null for completed and cancelled. |
Capture Modes
Each flow (or, by default, each organization) has a traceCaptureMode setting that controls how much detail is persisted per step. The setting also controls which SSE events are emitted.
| Mode | Step metadata (status, duration, tokens, cost) | inputContext / outputContext returned | step_input / step_output events |
|---|---|---|---|
off | Recorded as today (always) | null; inputSizeBytes / outputSizeBytes also null | Not emitted |
metadata_only (default) | Recorded | null; sizes populated | Not emitted |
full | Recorded | Full payload (truncated if oversized) | Emitted |
redacted | Recorded | Redacted payload | Emitted (already redacted) |
Step metadata is always recorded regardless of mode — capture mode only controls payload persistence. The trace endpoints therefore continue to return run summaries and step lists even under off; only the payload fields and sizes change.
Size cap. full and redacted modes cap each payload at 256 KB. Oversized payloads are stored truncated with truncated: true and a __truncated__: true marker injected at the JSON root of inputContext / outputContext.
Retention. Trace payloads are retained for 30 days by default; metadata (status, duration, tokens, cost) is kept indefinitely.
Capture mode resolution order: flow.traceCaptureMode → organization.defaultTraceCaptureMode → "metadata_only". Switching modes only affects runs that start after the change.
Per-Attempt Semantics
A step that retries produces one flow_step_runs record per attempt, and one trace record per attempt.
- The whole-run trace returns the latest attempt of every step.
- The single-step trace defaults to the latest attempt and lets you opt in to all attempts (
?attempt=all) or a specific one (?attempt=2). - The SSE live-tail stream emits a full event sequence per attempt. UI state machines should be keyed on
(stepId, attempt)so an in-flight attempt and a completed earlier attempt of the same step don't overwrite each other in your local store. (This is the same key the server uses internally for its own dedup, plus the event name.)
Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid auth token. |
| 403 | The caller has no access to the project that owns the flow run. |
| 404 | Flow run, step, or specific attempt not found. |
| 422 | Query parameter validation error — e.g. malformed attempt value. |
| 500 | Internal error reconstructing the trace. |
cURL Examples
List the 20 most recent runs of a flow:
Fetch the trace for a single run:
Inspect every retry attempt for one step:
Live-tail an in-progress run (curl prints each event as it arrives):
Minimal Node SSE consumer: