NOUKAI

Step-Through Execution API

Drive a flow one step at a time. Inspect outputs, edit them, and continue — server stays stateless between calls.

Base URL: https://api.noukai.xyz/api/v1/seq

The step-through endpoint runs a published flow one step at a time. Between calls the server holds no state — the client carries the cursor (executionId, stepIndex) and the accumulatedOutputs map. On each call you can inject inputOverrides (replace a prior step's output) or blockOverrides (edit a block's prompt/model before it runs).

Use it when you need:

  • A human-in-the-loop UI that shows each step's output before continuing.
  • "Edit and re-run from step N" flows.
  • Debugging or replaying flows with surgical input changes.

For straight end-to-end execution use /execute instead.

Endpoint

POST /seq/{org}/{project}/{flow}/step
POST /seq/{org}/{project}/{flow}/v{version}/step

The versioned form pins to a specific published version (recommended — stable tree across calls). The unversioned form uses the production version. Draft (v0) is not allowed: the tree must be stable for the cursor to be meaningful.

Path Parameters

ParameterDescription
orgOrganization slug
projectProject slug
flowFlow slug
version(optional) Published version number, e.g. v2

Authentication

Authorization: Bearer nk_live_...

Same auth as /execute — API keys or JWT both accepted.

Request Body

{
  "executionId": "uuid | null",
  "stepIndex": 0,
  "accumulatedOutputs": { "<stepId>": {} },
  "message": "string",
  "parameters": {},
  "inputOverrides": { "<stepId>": {} },
  "blockOverrides": { "<stepId>": { "prompt": "..." } },
  "runRemaining": false,
  "tools": [],
  "toolChoice": "auto",
  "toolCallMessages": [],
  "iterationsUsed": 0
}
FieldTypeRequiredDescription
executionIdstring | nullNonull on the first call (server creates one and returns it in run_started). Required on every subsequent call.
stepIndexintegerYes0-based cursor — which step to run next.
accumulatedOutputsobjectWhen stepIndex > 0Map of stepId → output from prior steps. The client owns this.
messagestringWhen stepIndex == 0Initial input (same channel as /execute). Ignored on later calls.
parametersobjectNoExtra initial inputs. First call only.
inputOverridesobjectNoReplacement values for prior outputs (data plane). Merged on top of accumulatedOutputs before deriving pipeline_input. Ignored at stepIndex == 0.
blockOverridesobjectNoPer-step config edits (config plane) — prompt, model, inputSchema, outputSchema, etc. Re-applied every call.
runRemainingbooleanNoIf true, run from stepIndex to the end without pausing. Streams step_progress between steps and ends with run_completed.
toolsarrayNoTool definitions for tool-calling blocks. Same shape as on /execute. See Tool Calls on /step.
toolChoicestring | objectNo"auto" | "none" | "required" | {"type":"function","function":{"name":"x"}}
toolCallMessagesarrayWhen resuming a tool pauseFull conversation carrying assistant tool_calls and the caller's role:"tool" results.
iterationsUsedintegerWhen resuming a tool pauseCumulative tool-call round-trips for the paused step. Echo back from the prior step_paused_for_tool_calls event.

Two override planes. inputOverrides rewrites the data flowing between steps (e.g. "pretend block A produced this instead"). blockOverrides rewrites the block configuration before execution (e.g. "use this prompt for block B"). They are independent — you can use both in one call.

Response

Content-Type: text/event-stream

The endpoint streams Server-Sent Events. Event names and payload shapes:

run_started (first call only)

{
  "executionId": "uuid",
  "flowId": "uuid",
  "totalSteps": 3,
  "steps": [
    { "index": 0, "blocks": [{ "stepId": "...", "blockName": "...", "processorType": "llm" }], "isParallel": false, "isLoop": false }
  ]
}

Save executionId — you must echo it back on every subsequent call.

block_started

{ "stepId": "block-uuid" }

block_completed

{
  "stepId": "block-uuid",
  "output": { "...": "..." },
  "durationMs": 1234,
  "tokens": { "input": 0, "output": 0 }
}

Merge output into your local accumulatedOutputs keyed by stepId.

step_paused — single-step mode, more steps remain

{
  "executionId": "uuid",
  "completedStepIndex": 0,
  "nextStepIndex": 1,
  "nextBlocks": [{ "stepId": "...", "blockName": "...", "processorType": "llm" }],
  "remainingCount": 2
}

Bump your local cursor to nextStepIndex and (optionally) wait for user input before the next call.

step_progressrunRemaining: true only

{
  "executionId": "uuid",
  "completedStepIndex": 0,
  "nextStepIndex": 1,
  "remainingCount": 2
}

step_paused_for_tool_calls — block paused for tool calls

Emitted instead of block_completed when the running block's model returns tool calls. The stream ends after this event; the run stays in_progress server-side until the client resumes.

{
  "runId": "uuid",
  "executionId": "uuid",
  "stepId": "block-uuid",
  "stepIndex": 1,
  "iterationsUsed": 1,
  "toolCallMessages": [
    { "role": "user", "content": "..." },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        { "id": "call_abc", "type": "function",
          "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }
      ]
    }
  ],
  "toolCalls": [
    { "id": "call_abc", "type": "function",
      "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }
  ],
  "accumulatedOutputs": {}
}
FieldDescription
runId / executionIdSame value; both emitted for client back-compat.
stepId, stepIndexThe paused block and its index in the flattened step plan.
iterationsUsedCumulative tool-call round-trips for this block. Echo back on resume.
toolCallMessagesFull conversation up to the pause. Append role:"tool" results and echo back.
toolCallsConvenience: the last assistant turn's tool_calls. Same IDs your resume call must answer.
accumulatedOutputsOutputs of blocks completed before this pause. Echo back.

run_completed

{
  "runId": "uuid",
  "status": "completed",
  "durationMs": 4567,
  "tokens": { "input": 0, "output": 0 }
}

Or, on failure:

{
  "runId": "uuid",
  "status": "failed",
  "durationMs": 1234,
  "error": "Step execution failed",
  "tokens": { "input": 0, "output": 0 }
}

Tool Calls

/step supports the same tool-calling loop as /execute, delivered over SSE: when the running block emits tool calls, the stream ends with step_paused_for_tool_calls and the client carries the conversation back on the next call.

Block prerequisites

The block must have processor_config.tools_enabled: true in the published flow version. Tool-calling blocks cannot live inside a parallel step or a loop in v1 (TOOLS_IN_NON_SEQUENTIAL_STEP).

Round-trip

  1. Send a fresh /step call with tools (and optional toolChoice) for the tool-calling block.
  2. If the model emits tool calls, the stream ends with step_paused_for_tool_calls. The run stays in_progress.
  3. Execute the calls in your process. Append each result as {"role":"tool","tool_call_id":"...","content":"..."} to toolCallMessages.
  4. POST /step again with the same stepIndex (do not advance) and:
    • executionId from the pause event,
    • toolCallMessages (extended with your tool results),
    • iterationsUsed echoed from the pause event,
    • tools re-sent unchanged so the model can call them again.
  5. The block re-enters its tool loop. It may pause again (multi-step tool use) or complete with block_completed; the run then continues to the next step normally.

Pause-on-resume IDs must match

Every id in the prior assistant tool_calls needs a matching role:"tool" message with that tool_call_id. Mismatches return 400 TOOL_RESULTS_MISMATCH before the stream starts.

tools / toolChoice are per-request

The server keeps no state between calls. Re-send tools (unchanged) and toolChoice on every call — fresh or resume — that should run a tool-enabled block. Omitting them means the next turn runs without tools available.

tools sent on a step whose block does not have tools_enabled: true is silently ignored for that step. 422 TOOLS_NOT_ENABLED only fires when no block in the flow has the flag on.

Limits

Same as /execute: 64 tools per request, 16 KB parameters schema, 256 KB per tool result content, 1 MB total toolCallMessages payload (returns 413 MESSAGES_TOO_LARGE), 25 iterations default per block (409 TOOL_ITERATION_LIMIT on overflow; override via the block's processor_config.max_tool_iterations).

Pipeline-Input Derivation

The server picks the next step's pipeline_input from the prior step's output (with overrides on top):

if stepIndex == 0:
    pipeline_input = { "message": <message>, ...parameters }
else:
    merged = { ...accumulatedOutputs, ...inputOverrides }
    prev   = steps[stepIndex - 1]
    if prev.isParallel:  pipeline_input = { sid: merged[sid] for sid in prev.stepIds }
    elif prev.isLoop:    pipeline_input = merged[prev.loopNodeId]
    else:                pipeline_input = merged[prev.primaryStepId]

This matches /execute semantics exactly, plus the inputOverrides injection.

Loops Are Atomic

A loop step runs all its iterations in one call — you cannot pause mid-iteration. The cursor pauses between loop steps and other steps, not inside a loop. Same constraint as the editor's test step-through.

Error Codes

All errors are HTTP errors (not SSE events) returned before the stream starts.

StatuscodeWhen
400MISSING_MESSAGEstepIndex == 0 and no message
400INVALID_STEP_INDEXstepIndex is out of range
400INVALID_TREEFlow's stored tree fails validation
400INVALID_VERSIONTried to use v0 (draft)
400NO_STEPSFlow has no steps tree (passthrough flow)
400STALE_TREEA stepId in accumulatedOutputs / inputOverrides no longer exists in the published tree (the flow was re-published mid-session). The body includes the new plan so the client can reset.
400TOOLS_INVALIDBad tool shape, duplicate name, oversized parameters, or oversized tool result content.
400TOOL_RESULTS_MISMATCHTool result IDs don't match the prior assistant tool_calls.
401Missing or invalid auth
402INSUFFICIENT_CREDITSOrg balance below threshold
404FLOW_NOT_FOUNDBad slug, or flow not published
404RUN_NOT_FOUNDexecutionId does not match any FlowRun
409TOOL_ITERATION_LIMITCumulative iterations reached the block's max_tool_iterations cap.
413MESSAGES_TOO_LARGEtoolCallMessages payload exceeds 1 MB.
422TOOLS_NOT_ENABLEDThe flow has no block with tools_enabled: true.
422TOOLS_IN_NON_SEQUENTIAL_STEPThe tools-enabled block sits inside a parallel or loop step.

STALE_TREE is the one error worth handling explicitly — it tells you the flow was re-published while your session was running. Recover by showing detail.steps to the user and starting a fresh session.

cURL Example

First call (start the run):

curl -N -X POST https://api.noukai.xyz/api/v1/seq/acme/support/triage/v3/step \
  -H "Authorization: Bearer $NOUKAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stepIndex": 0,
    "message": "I cannot reset my password"
  }'

Second call (advance cursor, carry outputs):

curl -N -X POST https://api.noukai.xyz/api/v1/seq/acme/support/triage/v3/step \
  -H "Authorization: Bearer $NOUKAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "11111111-2222-3333-4444-555555555555",
    "stepIndex": 1,
    "accumulatedOutputs": {
      "step-A-id": { "intent": "password_reset" }
    }
  }'

With an input edit (block A's output replaced before block B runs):

curl -N -X POST https://api.noukai.xyz/api/v1/seq/acme/support/triage/v3/step \
  -H "Authorization: Bearer $NOUKAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "11111111-2222-3333-4444-555555555555",
    "stepIndex": 1,
    "accumulatedOutputs": { "step-A-id": { "intent": "password_reset" } },
    "inputOverrides":     { "step-A-id": { "intent": "billing_question" } }
  }'

With tool calls — fresh call (block at stepIndex=1 has tools_enabled):

curl -N -X POST https://api.noukai.xyz/api/v1/seq/acme/support/triage/v3/step \
  -H "Authorization: Bearer $NOUKAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "11111111-2222-3333-4444-555555555555",
    "stepIndex": 1,
    "accumulatedOutputs": { "step-A-id": { "intent": "weather_check" } },
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }],
    "toolChoice": "auto"
  }'

If the stream ends with step_paused_for_tool_calls, resume with the same stepIndex and the tool results appended to toolCallMessages:

curl -N -X POST https://api.noukai.xyz/api/v1/seq/acme/support/triage/v3/step \
  -H "Authorization: Bearer $NOUKAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "11111111-2222-3333-4444-555555555555",
    "stepIndex": 1,
    "accumulatedOutputs": { "step-A-id": { "intent": "weather_check" } },
    "iterationsUsed": 1,
    "toolCallMessages": [
      { "role": "user", "content": "..." },
      { "role": "assistant", "content": null, "tool_calls": [
        { "id": "call_abc", "type": "function",
          "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }
      ]},
      { "role": "tool", "tool_call_id": "call_abc",
        "content": "{\"temp_c\":14,\"sky\":\"cloudy\"}" }
    ],
    "tools": [ /* same tools array */ ]
  }'

See Step-Through Execution Guide for full client implementations in TypeScript and Python.