Flow Node Reference
This is the complete reference for the 20 node types in the call-flow DSL. Each node’s data object is discriminated by its kind. The outcomes column lists the sourceHandle values you can draw edges from.
{ "kind": "entry", "trigger": "inbound" }| Field | Type | Notes |
|---|---|---|
trigger | "inbound" | "internal" | "api" | How the flow was entered. |
Outcomes: default.
Play a pre-recorded audio file (a media_file).
{ "kind": "play", "audioId": "welcome-msg" }| Field | Type | Notes |
|---|---|---|
audioId | string | Media id to play. |
Outcomes: default.
Speak text via TTS.
{ "kind": "say", "text": "Thanks for calling.", "voice": "en-US-neural-male" }| Field | Type | Notes |
|---|---|---|
text | string | Text to synthesize. |
voice | string? | Optional voice override. |
Outcomes: default.
Menu (IVR)
Section titled “Menu (IVR)”Play a prompt and branch on a DTMF keypress. Re-prompts on invalid/timeout up to the configured limits.
{ "kind": "menu", "promptText": "Press 1 for sales, 2 for support", "maxDigits": 1, "timeoutSec": 5, "maxFailures": 2, "bargeIn": true }| Field | Type | Notes |
|---|---|---|
promptAudioId / promptText | string? | First-entry prompt (audio or text). |
repromptAudioId | string? | Played on re-prompt. |
invalidAudioId | string? | Played before re-prompt on an unmapped key. |
minDigits / maxDigits | number | Digit count bounds (maxDigits required). |
terminators | string? | e.g. "#". |
timeoutSec | number | First-digit wait. |
interDigitTimeoutSec | number? | Wait between digits. |
maxFailures / maxTimeouts | number? | Give-up counters. |
bargeIn | boolean? | A keypress interrupts the prompt (default true). |
Outcomes: digit:0 … digit:9, digit:*, digit:#, timeout, invalid.
Collect
Section titled “Collect”Capture digits into a variable (PIN, account number), with optional regex validation.
{ "kind": "collect", "variable": "account_no", "maxDigits": 8, "terminator": "#", "regex": "^[0-9]{6,8}$", "maxTries": 3 }| Field | Type | Notes |
|---|---|---|
variable | string | Flow variable to store the digits in. |
promptAudioId / repromptAudioId / invalidAudioId | string? | Prompts. |
minDigits / maxDigits | number | Bounds. |
terminator | string? | e.g. "#". |
timeoutSec / interDigitTimeoutSec | number? | Timeouts. |
regex | string? | Validation pattern. |
maxTries | number? | Retry budget. |
bargeIn | boolean? | Interrupt the prompt. |
Outcomes: valid / default, invalid, timeout.
Dial a single target and bridge.
{ "kind": "dial", "target": "1001", "targetType": "user", "timeoutSec": 25 }| Field | Type | Notes |
|---|---|---|
target | string | User, extension, or PSTN number. |
targetType | "user" | "extension" | "pstn" | What target is. |
timeoutSec | number | Ring timeout. |
Outcomes: answered, no_answer, busy.
Ring Group
Section titled “Ring Group”Fork to the members of a ring group.
{ "kind": "ringGroup", "ringGroupId": "rg_01jdr70g9apz69vke5at748d47", "strategy": "ring_all", "timeoutSec": 25 }| Field | Type | Notes |
|---|---|---|
ringGroupId | string | The ring group to fork to. |
strategy | "ring_all" | "sequential" | "round_robin"? | Overrides the group’s strategy. |
timeoutSec | number | Overall ring timeout. |
Outcomes: answered, no_answer.
Enqueue
Section titled “Enqueue”Place the caller into an ACD queue.
{ "kind": "enqueue", "queueId": "q_01j160r23gw5zkpfjme3fwzs5k", "priority": 0 }| Field | Type | Notes |
|---|---|---|
queueId | string | The queue to join. |
priority | number? | Higher priority is served first. |
Outcomes: answered (bridged to agent), abandoned (caller hung up), timeout (max-wait exceeded), no_agents (no viable agent, overflow immediately), queue_full (at max_queued, deflected at join), ewt (estimated wait over threshold, deflected at join). See Overflow & Exit Destinations.
Voicemail
Section titled “Voicemail”Record a message to a mailbox.
{ "kind": "voicemail", "mailboxId": "vm_01jg8asp59ps9rtt7azhrhvcre", "greetingAudioId": "vm-greeting" }| Field | Type | Notes |
|---|---|---|
mailboxId | string | The voicemail box. |
greetingAudioId | string? | Custom greeting. |
Outcomes: recorded, no_message.
Conference
Section titled “Conference”Join a conference room (the media engine the conferencing module, also the 3-way primitive).
{ "kind": "conference", "roomId": "team-standup" }| Field | Type | Notes |
|---|---|---|
roomId | string | Conference room id. |
Outcomes: ended.
Forward
Section titled “Forward”Forward to an external number. See Call Forwarding for triggers, screening, and overflow.
{ "kind": "forward", "number": "12025550100", "cidMode": "did", "timeoutSec": 30, "confirm": true }| Field | Type | Notes |
|---|---|---|
number | string | E.164 destination (dialed via outbound routing). |
cidMode | "passthrough" | "did" | Caller-ID presentation. |
timeoutSec | number | Ring time before no-answer. |
confirm | boolean | Press-1 screening on the answered leg; unconfirmed → the overflow edge. |
Outcomes: answered, no_answer.
Transfer
Section titled “Transfer”Transfer the call. For agent-initiated blind/attended transfers, see Call Transfer.
{ "kind": "transfer", "target": "1002", "mode": "blind" }| Field | Type | Notes |
|---|---|---|
target | string | Transfer destination. |
mode | "blind" | "attended" | Transfer mode. |
Outcomes: default.
Set Variable
Section titled “Set Variable”Set a flow variable.
{ "kind": "setVar", "variable": "tier", "value": "gold" }Outcomes: default.
Condition
Section titled “Condition”Branch on an expression evaluated against the flow’s variables (cond_route in the legacy vocabulary). Resolved inside the stateful edge object with a recursive-descent evaluator, no edge round-trip. There are two forms: an expr boolean test, or a switchOn per-case switch.
{ "kind": "condition", "expr": "({vip}==1||{digits}>40)&&{from}^+1" }| Field | Type | Notes |
|---|---|---|
expr | string? | A boolean expression. Ignored if switchOn is set. |
switchOn | string? | A value (interpolated) to switch on, matched per-case against edge sourceHandles. |
Operators (expr): == != (numeric-aware: if both sides parse as numbers they compare numerically, otherwise as strings), > < >= <= (numeric; a non-numeric side is always false), ~ (contains), !~ (not-contains), ^ (starts-with), $ (ends-with), ~= (regex match, right side is the pattern). Combine with boolean algebra &&, ||, !, and parens, with normal precedence (! tightest, then &&, then ||). A bare operand with no operator is a truthiness test (empty string, "0", "false", "null", and "undefined" are falsy).
Operands interpolate {var} against the flow’s variable bag (only identifier-shaped names, so regex quantifiers like {10} or {2,3} inside a ~= pattern survive un-interpolated). Wrap an operand in '…' or "…" to protect spaces or operator characters; unquoted multi-word operands still join with a space for back-compat. A malformed or unparseable expression evaluates to false, the safe edge, rather than erroring the call.
Per-case (switchOn): set switchOn instead of expr to branch on a value directly. The outcome is case:<value> (after interpolation), matched against an edge whose sourceHandle is exactly that string (e.g. switchOn: "{dept}" with dept=sales produces outcome case:sales, routed by an edge with sourceHandle: "case:sales"). No matching case falls through to the default edge.
Outcomes: true, false (via expr); case:<value> (via switchOn, unmatched values fall through to the default edge).
HTTP Request
Section titled “HTTP Request”Call an external API and branch on the result. Resolved inside the stateful edge object (the session object performs the fetch directly).
{ "kind": "httpRequest", "url": "https://api.example.com/lookup", "method": "GET", "saveAs": "lookup" }| Field | Type | Notes |
|---|---|---|
url | string | Endpoint. |
method | string | HTTP method. |
saveAs | string? | Save the response into this variable. |
Outcomes: 2xx, error.
Time Condition
Section titled “Time Condition”Branch on a business-hours schedule. Resolved inside the stateful edge object.
{ "kind": "timeCondition", "scheduleId": "sch_01j0jvpe0wzc6g05bktfew0xyx" }| Field | Type | Notes |
|---|---|---|
scheduleId | string | The time_schedule to evaluate. |
Outcomes: in_hours, out_of_hours, holiday (falls back to out_of_hours if undrawn).
Callback
Section titled “Callback”Request-a-callback: enqueues the caller (vars.from) as a campaign_contact lead, status = pending, into the given outbound-dialer campaign. The campaign_contact row is tagged flow_callback and carries the originating callId. The Outbound Dialer then calls the lead back like any other campaign contact.
{ "kind": "callback", "campaignId": "camp_01jzr0m1v3f8x8f5c3q9k1r0aq" }| Field | Type | Notes |
|---|---|---|
campaignId | string? | The callback campaign to enqueue into. Must belong to the same account. |
listId | string? | Legacy alias for campaignId, honored if campaignId is absent. |
The node is account-scoped (the campaign must belong to the calling account) and de-duplicates: if the caller already has a lead in that campaign with status pending, dialing, or retry, no duplicate row is inserted. This is fire-and-continue: the lead is enqueued and the flow immediately continues on the default edge, typically into a say + hangup telling the caller they’ll be called back. It’s distinct from the in-queue Tier 2 virtual-queue callback (which keeps the caller’s place in a live queue and calls back via the ACD reserve fence); this node always creates a fresh dialer lead.
Outcomes: default.
Last Called User
Section titled “Last Called User”Sticky-route a repeat caller back to the last agent who handled them (legacy last_called_user). Unlike per-queue sticky/last-agent routing (which is scoped to one queue’s affinity table), this node checks the caller’s most recent affinity across all queues on the account.
{ "kind": "lastCalledUser", "timeframeMin": 1440, "timeoutSec": 25, "fallbackNodeId": "supQ" }| Field | Type | Notes |
|---|---|---|
timeframeMin | number? | Affinity window in minutes. Default 10080 (7 days); pairings older than the window are ignored. |
timeoutSec | number? | Ring timeout for the dial to the last agent. Default 25. |
fallbackNodeId | string? | If no dispatchable last agent is found, jump straight to this node id instead of following the none edge. |
The lookup only returns an agent who is currently dispatchable: available, in waiting state, reachable, not mid external-call, and not in wrap-up. A last agent who is logged out, busy, or in wrap-up is never rung, since the session object join filters them out before the caller is dialed. On a match, the target agent id is stashed in vars.last_agent and the flow dials that user directly (a no-answer on that dial falls through to the node’s default edge, not none). On no match, the outcome is none, unless fallbackNodeId is set, in which case the flow jumps directly to that node instead of requiring a drawn none edge.
Outcomes: none (no dispatchable last agent, and no fallbackNodeId); dial outcomes (answered, no_answer) flow through the default edge when a last agent is found and dialed.
Flow (nested)
Section titled “Flow (nested)”Route into another published flow. The sub-flow’s entire node graph is spliced inline at load time (not at runtime): the interpreter never actually sees a flow node, it sees the target flow’s nodes and edges merged directly into the graph it’s walking, with every id namespaced to avoid collisions. This is the mechanism for reusable sub-flows, a shared “check business hours” or “collect a callback number” block you build once and reference from many flows.
{ "kind": "flow", "flowId": "flow_01jzr1c2b8x2s0f8v3n7m5t1qz" }| Field | Type | Notes |
|---|---|---|
flowId | string | The id of the published flow to splice in. Must belong to the same account and have is_published = 1. |
Splicing is destination semantics: there is no implicit return to the parent flow after the sub-flow finishes; the sub-flow’s own terminal nodes (its hangup, or bridges it starts) decide how the call ends. If you need to “come back,” end the sub-flow by routing into a shared continuation, or design the parent to not need one.
Resolution is depth-bounded (nested flows may reference further nested flows up to 4 levels deep) and size-bounded (a spliced graph is capped at 400 nodes total), so a broken reference, an unpublished flow, or an accidental cycle can never strand a caller: the offending branch is terminated with a fail-safe hangup (cause subflow_unresolved) instead of erroring or looping. A flow node can also appear as a synthesized routing destination (for example a queue’s timeout exit pointed at a flow), not just as a visual-builder node; both paths resolve through the same splice logic.
Outcomes: none, this node is always resolved away before the interpreter runs (terminal from the graph’s perspective: it’s replaced by the spliced sub-graph’s own entry node).
Hangup
Section titled “Hangup”End the call.
{ "kind": "hangup", "cause": "normal_clearing" }| Field | Type | Notes |
|---|---|---|
cause | string? | Optional SIP/Q.850 cause. |
Outcomes: terminal (no outgoing edges).