Workflow definitions
The workflow definition contract (v1): every field of nodes and edges, the validation rules, and a complete working example.
This page is the field-by-field reference for the workflow definition — the JSON graph you save via the Workflows API or build visually in the dashboard. For the concept and how workflows interact with assistants, start at Workflows.
Top level
| Field | Type | Required | Description |
|---|---|---|---|
version | integer | Yes | Contract version. Currently always 1. |
entry_node | string | Yes | Node id where every call starts. Must be a conversation node. |
global_prompt | string | No | Prepended to the instructions of every conversation node. |
nodes | array | Yes | The steps. At least one node. |
edges | array | No | The transitions between steps. |
Node ids are lowercase: letters, digits and underscores, starting with a letter (^[a-z][a-z0-9_]{0,40}$), and must be unique.
Conversation node
The talking step. The entry node is always a conversation node.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique node id |
type | string | Yes | conversation |
instructions | string | Yes | System prompt for this step (after the global_prompt) |
first_line | string | No | Fixed line spoken when the step becomes active. On realtime models it is passed as an opening hint instead of spoken verbatim. |
global | boolean | No | Reachable from every conversation node without edges |
global_description | string | If global | When a caller should be brought here |
llm_config | object | No | Per-step model override: provider, model, temperature |
voice | string | No | Voice swap within the assistant's provider (realtime: the speech-to-speech voice; pipeline: the TTS voice). Mutually exclusive with tts_config. |
tts_config | object | No | Full text-to-speech override incl. provider parameters (pipeline only) |
stt_config | object | No | Speech-recognition override: provider, model, language, keyterms (pipeline only) |
tools | array | No | Tools available in this step, same format as assistant tools |
ui | object | No | Canvas position (x, y). Ignored by the calling engine. |
Global nodes. A global conversation node gets an implicit transition from every other conversation node, described by its global_description. An explicit edge to the same node takes precedence. Global nodes also count as reachability roots — a graph section only reachable via a global node is valid.
Per-step models. llm_config is allowed on conversation nodes only, and requires a pipeline (non-realtime) assistant on the number. All overrides are constructed once at call start, fail-closed: an unknown model rejects the call before pickup instead of failing mid-conversation. The entry node's override applies to the whole session.
Per-step voices and speech. voice works on both modes as a voice swap within the assistant's provider (on realtime assistants a new speech connection with the other voice is prepared per step). tts_config and stt_config are full replacements of the assistant's speech configs for that step — replace, not merge, so a provider switch never carries half the old provider's parameters. Both are pipeline-only and reject on realtime assistants with a developer-readable reason; the entry node's overrides apply to the whole session. All of it prebuilds fail-closed at call start.
Tool node
A background step: the platform calls the webhook itself and advances automatically.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique node id |
type | string | Yes | tool |
tool_name | string | Yes | Name sent in the tool-calls webhook |
url | string | No | Per-node URL override; defaults to the webhook resolved for the call |
arguments | object | No | Static arguments sent with the call |
first_line | string | No | Hold line spoken while the call runs |
ui | object | No | Canvas position. Ignored by the engine. |
The result (or the error, if the call fails) is placed in the conversation context, and the flow advances along the node's single outgoing edge either way — the next step can see and react to what happened. For arguments the assistant should collect from the caller during the conversation, use a regular tool on a conversation node; tool-node arguments are static.
Transfer node
Hands the call to a human. Terminal.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique node id |
type | string | Yes | transfer |
destination | string | Yes | E.164 phone number (+31612345678) |
first_line | string | No | Announcement spoken once before dialing |
ui | object | No | Canvas position. Ignored by the engine. |
The platform speaks the announcement and starts the transfer as soon as the node becomes active — the action is executed in code and cannot be forgotten or repeated. The number needs a SIP trunk that allows transfers.
End node
Ends the call politely. Terminal.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique node id |
type | string | Yes | end |
first_line | string | No | Goodbye line; defaults to a neutral thank-you |
ui | object | No | Canvas position. Ignored by the engine. |
Edges
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Source node id (conversation or tool nodes only) |
to | string | Yes | Target node id |
description | string | Yes* | When the assistant should take this transition. Optional on edges leaving a tool node. |
message | string | No | Line spoken during the hand-over |
Write descriptions as observable conditions — "The caller asks about opening hours" — not instructions. The assistant reads them to decide where the conversation goes next.
Validation rules
Definitions are validated when you save them (API and dashboard alike) and again, fail-closed, when a call comes in. The full rule set:
versionmust be1;nodesmust be a non-empty array.- Node ids match
^[a-z][a-z0-9_]{0,40}$and are unique. entry_nodereferences an existing conversation node.- Conversation nodes require non-empty
instructions. - Transfer nodes require a valid E.164
destination. - Tool nodes require a non-empty
tool_nameand exactly one outgoing edge. - End and transfer nodes cannot have outgoing edges.
- Edge endpoints must exist; no self-loops; no duplicate
from/topairs. - Edge
descriptionis required, except on edges leaving a tool node. globalis allowed on conversation nodes only and requires aglobal_description.llm_config,voice,tts_configandstt_configare allowed on conversation nodes only;voicemust be a non-empty string,tts_config/stt_configmust be objects, andvoiceandtts_configare mutually exclusive.- A node's own tool names must not collide with the generated transition tools (
go_to_<node id>). - Every node must be reachable from
entry_node(global nodes count as extra roots).
Violations are returned as a details array, one specific message per problem:
{
"error": "Invalid workflow definition",
"details": [
"node 'human': transfer nodes require destination in E.164 format (e.g. +31612345678)",
"node 'lookup': tool nodes require exactly one outgoing edge (got 0)"
]
}Complete example
A dental practice reception: general questions at the reception, an availability lookup as a background step, a scheduling specialist on a stronger model, a human transfer on request, and a clean goodbye. The reception is global, so "put me back to the reception" works from anywhere.
{
"version": 1,
"entry_node": "reception",
"global_prompt": "You work for Riverside Dental. Be warm and concise.",
"nodes": [
{
"id": "reception",
"type": "conversation",
"instructions": "You are the receptionist. Handle general questions about the practice. When the caller asks about availability, hand off to the availability check.",
"first_line": "Good afternoon, Riverside Dental. How can I help you?",
"global": true,
"global_description": "The caller wants the reception back or has a general question."
},
{
"id": "check_availability",
"type": "tool",
"tool_name": "check_availability",
"url": "https://api.example.com/availability",
"arguments": { "location": "riverside", "days_ahead": 7 },
"first_line": "One moment while I look that up for you."
},
{
"id": "specialist",
"type": "conversation",
"instructions": "You are Sam, the scheduling specialist. Use the availability result from the context to help the caller pick a slot.",
"first_line": "You are speaking with Sam, the scheduling specialist.",
"llm_config": { "provider": "openai", "model": "gpt-5.4-mini" }
},
{
"id": "human",
"type": "transfer",
"destination": "+31201234567",
"first_line": "I am transferring you to a colleague now, one moment please."
},
{
"id": "goodbye",
"type": "end",
"first_line": "Thanks for calling Riverside Dental. Have a great day!"
}
],
"edges": [
{
"from": "reception",
"to": "check_availability",
"description": "The caller asks about available appointment slots.",
"message": "Let me check that for you."
},
{ "from": "check_availability", "to": "specialist" },
{
"from": "specialist",
"to": "human",
"description": "The caller explicitly asks for a human employee."
},
{
"from": "reception",
"to": "goodbye",
"description": "The caller indicates the conversation is finished."
},
{
"from": "specialist",
"to": "goodbye",
"description": "The caller indicates the conversation is finished."
}
]
}Workflows
Build multi-step call flows on a visual canvas: conversation steps, background tool calls, human transfers and clean endings — with one assistant supplying the voice and call settings.
Web Calls
Let users speak directly to an AI assistant from their browser via WebRTC, no phone number required.