Skip to main content
The Interrupt node (type: "interrupt") pauses a running workflow to ask a person for input or approval, then resumes from exactly where it stopped once that person answers. It is the workflow engine’s human-in-the-loop (HITL) primitive: instead of automating a decision, you hand it to a human and wait for a structured value to come back. Use it whenever a run must not proceed without a human in the loop — approving a refund before it posts, choosing between drafted responses, supplying a value the workflow cannot compute, or gating a destructive tool call. The node is one of the nine node types the workflow engine compiles, and it is backed by the engine’s checkpointer: a paused run is durably persisted in managed storage, so it can sit interrupted for as long as you need and survive a process restart.
The Interrupt node is the workflow-engine pause primitive. It is distinct from the chat HITL used by the AI Composer and the Assistant, which pause with a user_input_request event and mint a new run_id on resume. The Interrupt node pauses with an interrupt event and resumes on the same run_id. Both contracts are mapped side by side in Human-in-the-loop (HITL) resume.

How the pause works

When a run reaches an enabled Interrupt node, the engine:
1

Resolves the message

Any {{node_id.field}} references in message are resolved against the current run state, so the prompt the human sees can quote earlier results.
2

Builds the interrupt value

The node assembles a structured value {message, data} and adds resume_schema and examples if you configured them. data carries any legacy data_keys snapshot (see Deprecated fields).
3

Calls interrupt() and pauses

The node raises an interrupt. Execution stops at this node, the run state is checkpointed to managed storage, and the run status is set to interrupted.
4

Emits the interrupt event

The executor publishes a single interrupt event on the run’s SSE stream carrying the resolved message, data, schema, and examples. The stream then goes quiet — there is no terminal frame (see The interrupt event).
5

Waits for resume

The run waits indefinitely until you call the resume endpoint. On resume, the value you send is stored in run state under the node’s id, and execution continues to the next node.

Inputs and outputs

Input. The Interrupt node reads run state to resolve {{...}} references inside message (and inside any legacy data_keys). It has no input_mapping of its own. Output. When the run resumes, the resume value you supply is written to run state under the node’s id, following the engine-wide convention that every node writes its result under its own id. Downstream nodes read it with a reference such as {{node_approval.approved}}.
There is no output_key. The result is always stored under the node id. output_key exists on the config only as a deprecated no-op (see Deprecated fields).
any
The resume value passed to POST /workflows/resume. If you send a JSON object as resume_value, the whole object is stored under the node id — for example {"approved": true, "notes": "Approved by manager"} is read downstream as {{node_approval.approved}} and {{node_approval.notes}}. A scalar resume value is stored as-is.

Configuration

The node carries an interrupt_config object of type InterruptNodeConfig. In the builder, these fields are edited in the node’s detail panel; over the API they live under the node definition.
string
required
The prompt shown to the human. Supports {{node_id.field}} references, so you can quote upstream results — for example Approve sending {{node_draft.subject}} to {{node_lookup.email}}?. References are resolved at pause time against the current run state; any reference that cannot be resolved is left intact in the string.
object
An optional JSON Schema describing the shape of the value the human is expected to return. It is forwarded verbatim in the interrupt event so the UI or SDK consumer can render and validate the answer form. The engine does not enforce this schema on the resume value — it is advisory metadata for the client, not a server-side validator. To hard-validate the returned value, add a downstream Guardrails node or Conditional node.
object
Optional example resume values, forwarded verbatim in the interrupt event to help a client render choices or defaults — for example {"approve": {"approved": true}, "reject": {"approved": false}}. Advisory only; not validated by the engine.
array
deprecated
Deprecated. A list of state keys to snapshot into the event’s data object. Use {{node_id}} references inside message instead. When set, the engine copies each listed key from run state into data; when empty (the default), data is {}.
string
deprecated
Deprecated no-op. The output is always stored under the node id; this field is ignored.

Deprecated fields

data_keys and output_key predate the {{node_id.field}} reference system. Do not author new workflows with them:
  • Replace data_keys by inlining the values you want the human to see directly in message with references, e.g. Refund {{node_order.amount}} for order {{node_order.id}}?.
  • Ignore output_key entirely; the resume value always lands under the node id.

The interrupt event

While paused, the run is observable on GET /workflows/listen/{run_id} — the standard run SSE stream. The Interrupt node produces one interrupt frame, a wrapped event whose data object carries the resolved message and the forwarded schema and examples:
Interrupt frame on the run SSE stream
Important properties of this event, all verified against the executor:
  • interrupt is not a terminal event. The stream does not close after it; it goes quiet and idles on heartbeat frames. The run status is interrupted, persisted as a terminal run row, but the live SSE stream has no done/error/cancelled frame at the pause.
  • interrupted is a history-only marker. A separate interrupted type is appended to the run’s event history (used to stop history replay on reconnect); it is never published live. A client watching live sees the interrupt event, not an interrupted one.
  • The discriminator is the JSON type field. The run stream is data-only — frames are data: <json>\n\n with no SSE event: line. Parse data and switch on .type.
For the full run-event taxonomy and framing, see SSE run streaming.

Resuming the run

You resume a paused run by sending the human’s answer to the resume endpoint. The run continues on the same run_id from the checkpoint where it stopped.
any
required
The value to inject as the interrupt result. Stored under the Interrupt node’s id. Send a JSON object to expose multiple fields downstream.
string
required
The run_id of the interrupted run. The endpoint returns 400 if it is missing and binds it to the resumed thread.
string
The deployed workflow to load the schema from (database mode). Provide this or an inline workflow definition (ad-hoc mode).
object
An inline workflow definition (ad-hoc mode). Mutually exclusive with workflow_id.
The thread_id is the checkpoint identifier and is passed in the URL path (POST /workflows/resume/{thread_id}). On resume, the database default input and config are ignored — the run continues from the persisted checkpoint state, and resume_value is the interrupt answer, not new input.
The moment the run resumes, the executor publishes a resumed event on the same run_id stream so any listener observes the continuation, then normal node_update frames follow as downstream nodes execute:
Resume continuation on the same run_id stream
For the SDK consumption patterns around answering an interrupt while streaming, see Streaming & HITL.

No streaming branch

The Interrupt node has no token-streaming branch. Token-level streaming exists only on the LLM node in its simple-LLM/messages-state mode; the Interrupt node neither calls a model nor emits partial output. Its only stream emission is the single, complete interrupt event when it pauses and the resumed event when it continues. There is nothing to stream incrementally — the node’s job is to stop and wait.

Retry behavior

The Interrupt node is the one node type the engine does not retry-wrap. A pause is not a failure, so it would make no sense to retry it, and retry_config has no effect on an Interrupt node. By contrast, every other node type (LLM, tool, agent, function, conditional, transformer, guardrails, knowledge) is wrapped with retry-and-events handling. See Error handling & retries.

Credit impact

The Interrupt node itself consumes no credits. The workflow engine has no fixed per-node charge; credits are metered only by the operations that incur cost — LLM and agent token usage, and managed knowledge retrieval. Pausing and resuming are free. A run can stay interrupted indefinitely at no credit cost. Resuming a run is not billing-gated. The resume route (POST /workflows/resume/{thread_id}) does not call the admission gate; that gate is live only on the run, composer, assistant, and managed-knowledge surfaces. A resume continues from the checkpoint regardless of your organization’s credit or rate-limit state.

Errors

HTTPException
The resume request is missing resume_value or run_id. Shape: {detail: "..."}. Resume also returns 400 if the run is not actually in an interrupted/running state.
HTTPException
The resume endpoint requires an organization owner or admin (the member role is retired). A missing or invalid Authorization: Bearer token returns 401; an authenticated non-owner/admin returns 403. Shape: {detail: "..."}. See Roles & permissions.
HTTPException
The thread_id/run_id does not exist or is not owned by your organization. Ownership failures return an identical 404 (not 403) by design, so there is no existence leak. Shape: {detail: "..."}.
HTTPException
An unexpected failure while loading the checkpoint or resuming the run. Shape: {detail: "..."}.
build-time
A node of type: "interrupt" without an interrupt_config fails workflow build/validation before the run starts. The message may be empty — InterruptNodeConfig.message has no minimum length — but the interrupt_config object itself must be present.
In-run failures elsewhere in the workflow surface as node_error frames carrying a stable reason code; the Interrupt node does not raise during the pause itself — it simply waits.

Worked example: approve before sending

A three-node flow drafts an email with an LLM node, pauses for a human to approve, and only sends on approval. The Conditional node after the interrupt branches on the approval the human returned. When the run reaches node_approval, it pauses and emits the interrupt event with the resolved message (the draft inlined via {{node_draft}} — the LLM node writes its text under its own id, so {{node_draft}} resolves to the draft string), the resume_schema, and the examples. A reviewer answers by calling POST /workflows/resume/{thread_id} with resume_value: {"approved": true}. The value lands under node_approval, the Conditional node reads {{node_approval.approved}}, and the run routes to node_send. Had the reviewer sent {"approved": false, "notes": "..."}, the Conditional node’s default_target would route to the rejection branch instead.

Human-in-the-loop (HITL) resume

The two pause/resume contracts compared: the workflow Interrupt node vs the chat HITL user_input_request.

SSE run streaming

The run event stream that carries the interrupt and resumed events.

Variables & references

The {{node_id.field}} reference system used in the message and read downstream.

Node types overview

All nine node types and how each writes its result into run state.