Features

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

FieldTypeRequiredDescription
versionintegerYesContract version. Currently always 1.
entry_nodestringYesNode id where every call starts. Must be a conversation node.
global_promptstringNoPrepended to the instructions of every conversation node.
nodesarrayYesThe steps. At least one node.
edgesarrayNoThe 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.

FieldTypeRequiredDescription
idstringYesUnique node id
typestringYesconversation
instructionsstringYesSystem prompt for this step (after the global_prompt)
first_linestringNoFixed line spoken when the step becomes active. On realtime models it is passed as an opening hint instead of spoken verbatim.
globalbooleanNoReachable from every conversation node without edges
global_descriptionstringIf globalWhen a caller should be brought here
llm_configobjectNoPer-step model override: provider, model, temperature
voicestringNoVoice swap within the assistant's provider (realtime: the speech-to-speech voice; pipeline: the TTS voice). Mutually exclusive with tts_config.
tts_configobjectNoFull text-to-speech override incl. provider parameters (pipeline only)
stt_configobjectNoSpeech-recognition override: provider, model, language, keyterms (pipeline only)
toolsarrayNoTools available in this step, same format as assistant tools
uiobjectNoCanvas 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.

FieldTypeRequiredDescription
idstringYesUnique node id
typestringYestool
tool_namestringYesName sent in the tool-calls webhook
urlstringNoPer-node URL override; defaults to the webhook resolved for the call
argumentsobjectNoStatic arguments sent with the call
first_linestringNoHold line spoken while the call runs
uiobjectNoCanvas 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.

FieldTypeRequiredDescription
idstringYesUnique node id
typestringYestransfer
destinationstringYesE.164 phone number (+31612345678)
first_linestringNoAnnouncement spoken once before dialing
uiobjectNoCanvas 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.

FieldTypeRequiredDescription
idstringYesUnique node id
typestringYesend
first_linestringNoGoodbye line; defaults to a neutral thank-you
uiobjectNoCanvas position. Ignored by the engine.

Edges

FieldTypeRequiredDescription
fromstringYesSource node id (conversation or tool nodes only)
tostringYesTarget node id
descriptionstringYes*When the assistant should take this transition. Optional on edges leaving a tool node.
messagestringNoLine 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:

  • version must be 1; nodes must be a non-empty array.
  • Node ids match ^[a-z][a-z0-9_]{0,40}$ and are unique.
  • entry_node references 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_name and exactly one outgoing edge.
  • End and transfer nodes cannot have outgoing edges.
  • Edge endpoints must exist; no self-loops; no duplicate from/to pairs.
  • Edge description is required, except on edges leaving a tool node.
  • global is allowed on conversation nodes only and requires a global_description.
  • llm_config, voice, tts_config and stt_config are allowed on conversation nodes only; voice must be a non-empty string, tts_config/stt_config must be objects, and voice and tts_config are 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."
    }
  ]
}

On this page