Skip to main content
Conversation flows let you define structured, multi-step call scripts for your AI agents. Instead of relying on a single prompt, you can build a directed graph of nodes (steps) and edges (transitions) that guide the conversation through a precise sequence. Flows are defined as a JSON format that we call the flow definition. You can build flows visually in the RevRing dashboard, or generate them programmatically — including with AI tools like ChatGPT — and import them via the API.

Overview

A flow consists of:
  • Begin — which node starts the conversation and who speaks first
  • Nodes — individual steps (conversation, tool call, logic branch, transfer, etc.)
  • Edges — connections between nodes that define transitions and conditions
{
  "schemaVersion": 1,
  "begin": { "startNodeId": "node_1", "whoSpeaksFirst": "agent" },
  "nodes": [ ... ],
  "edges": [ ... ]
}

Flow Definition Reference

Top-Level Structure

FieldTypeRequiredDescription
schemaVersion1YesMust be 1
beginobjectYesFlow entry point configuration
nodesarrayYesList of flow nodes (at least one)
edgesarrayYesList of edges connecting nodes
uiobjectNoVisual editor state (viewport position/zoom)

Begin

FieldTypeRequiredDescription
startNodeIdstringYesID of the first node to execute. Must match a node in nodes.
whoSpeaksFirst"agent" or "user"YesWhether the agent speaks first or waits for the caller

Node Types

Every node has the following base fields:
FieldTypeRequiredDescription
idstringYesUnique identifier for the node
typestringYesOne of the node types below
namestringYesHuman-readable label
isGlobalbooleanNoIf true, this node can be triggered from any point in the flow via a global edge
position{ x, y }YesVisual position in the editor (use any values when generating programmatically)
dataobjectYesType-specific configuration (see below)

Conversation

The core node type. The agent speaks and/or listens based on an instruction. Type: "conversation"
FieldTypeRequiredDescription
instructionType"prompt" or "static"Yes"prompt" = LLM interprets the instruction as context. "static" = agent speaks the text verbatim.
instructionstringYesThe instruction text or static message. Supports {{variables}}.
skipResponsebooleanNoIf true, the agent speaks the instruction and immediately moves to the next node without waiting for a user reply. Requires a skip edge.
blockInterruptionsbooleanNoIf true, the agent cannot be interrupted while speaking
Example:
{
  "id": "greeting",
  "type": "conversation",
  "name": "Greeting",
  "position": { "x": 300, "y": 200 },
  "data": {
    "instructionType": "prompt",
    "instruction": "Greet the caller warmly. Introduce yourself as {{agent_name}} from {{company_name}}. Ask how you can help them today."
  }
}

Function (Tool Call)

Invokes one of your agent’s custom tools during the flow. Type: "function"
FieldTypeRequiredDescription
toolNamestringYesName of the custom tool to invoke (must be configured on the agent)
speakDuringExecutionbooleanNoIf true, agent speaks while the tool runs
speakInstructionstringNoWhat to say while the tool executes
speakInstructionType"prompt" or "static"NoHow to interpret the speak instruction
blockInterruptionsbooleanNoBlock interruptions during execution
waitForResultbooleanNoDefault true. Set false for fire-and-forget calls.
outputVariablesarrayNoMap tool output keys to flow variables for use in downstream conditions
Each entry in outputVariables:
FieldTypeDescription
outputKeystringKey in the tool’s JSON response
variableNamestringFlow variable name to store the value as
Example:
{
  "id": "lookup_order",
  "type": "function",
  "name": "Look Up Order",
  "position": { "x": 300, "y": 400 },
  "data": {
    "toolName": "lookup_order",
    "speakDuringExecution": true,
    "speakInstruction": "Let me look that up for you, one moment.",
    "speakInstructionType": "static",
    "outputVariables": [
      { "outputKey": "status", "variableName": "order_status" },
      { "outputKey": "eta", "variableName": "delivery_eta" }
    ]
  }
}

Logic Split

A branching node with no data of its own — all logic is defined by its outgoing edges (conditions and an else fallback). Type: "logic_split"
FieldTypeRequiredDescription
(none)Data is an empty object {}
The logic split node must have:
  • One or more condition edges (evaluated in order)
  • Exactly one else edge (fallback if no conditions match)
Example:
{
  "id": "check_status",
  "type": "logic_split",
  "name": "Check Order Status",
  "position": { "x": 300, "y": 600 },
  "data": {}
}

Call Transfer

Transfers the call to another phone number. Type: "call_transfer"
FieldTypeRequiredDescription
transferTostringYesDestination phone number in E.164 format (e.g. "+14155551234") or a {{variable}}
transferMode"cold" or "warm"NoCold = immediate transfer. Warm = agent briefs the recipient first.
speakDuringExecutionbooleanNoSpeak while the transfer is being set up
speakInstructionstringNoWhat to say during transfer
holdMessagestringNoMessage spoken to the caller while on hold (warm transfer, max 500 chars)
holdMusicEnabledbooleanNoPlay hold music (warm transfer)
summaryPromptstringNoPrompt used to generate a brief for the recipient (warm transfer, max 2000 chars)
introMessagestringNoMessage spoken to the recipient before connecting (warm transfer, max 500 chars)
Example:
{
  "id": "transfer_support",
  "type": "call_transfer",
  "name": "Transfer to Support",
  "position": { "x": 600, "y": 800 },
  "data": {
    "transferTo": "+12025559999",
    "transferMode": "warm",
    "speakDuringExecution": true,
    "speakInstruction": "Let me connect you with our support team.",
    "holdMessage": "Please hold while I connect you.",
    "holdMusicEnabled": true
  }
}

End Call

Terminates the call, optionally speaking a closing message. Type: "end"
FieldTypeRequiredDescription
messagestringNoClosing message to speak before hanging up
Example:
{
  "id": "goodbye",
  "type": "end",
  "name": "End Call",
  "position": { "x": 300, "y": 1000 },
  "data": {
    "message": "Thank you for calling. Have a great day!"
  }
}

Press Digit (DTMF)

Waits for the caller to press a phone keypad digit. Useful for IVR-style menus or entering account numbers. Type: "press_digit"
FieldTypeRequiredDescription
instructionstringYesWhat to say before listening for a digit
detectionDelaySecondsnumberNoHow long to wait for input (0–10 seconds, default 1)
Example:
{
  "id": "menu",
  "type": "press_digit",
  "name": "Main Menu",
  "position": { "x": 300, "y": 200 },
  "data": {
    "instruction": "Press 1 for sales, press 2 for support, or press 3 for billing.",
    "detectionDelaySeconds": 3
  }
}

Extract Variable

Uses the LLM to extract structured data from the conversation and store it in flow variables for downstream use. Type: "extract_variable"
FieldTypeRequiredDescription
variablesarrayYesList of variables to extract (at least one)
Each entry in variables:
FieldTypeRequiredDescription
variableNamestringYesName of the flow variable to store the extracted value
descriptionstringYesDescription of what to extract (used as context for the LLM)
variableType"text", "number", "enum", "boolean"YesData type of the variable
enumOptionsstring[]NoRequired when variableType is "enum" — the allowed values
Example:
{
  "id": "get_intent",
  "type": "extract_variable",
  "name": "Detect Intent",
  "position": { "x": 300, "y": 400 },
  "data": {
    "variables": [
      {
        "variableName": "caller_intent",
        "description": "The primary reason the caller is calling",
        "variableType": "enum",
        "enumOptions": ["billing", "support", "sales", "other"]
      },
      {
        "variableName": "caller_name",
        "description": "The caller's first name if mentioned",
        "variableType": "text"
      }
    ]
  }
}

Edges

Edges connect nodes and define how the flow transitions between steps.
Condition edges can be attached to any node type, not just logic split nodes. For example, you can put condition edges directly on a conversation node to branch based on the caller’s response — no logic split needed.

Edge Fields

FieldTypeRequiredDescription
idstringYesUnique identifier for the edge
sourcestringYesID of the source node, or "__global__" for global edges
targetstringYesID of the target node
kindstringYesOne of: "default", "condition", "else", "skip"
orderintegerCondition edges onlyEvaluation order (0-based). Lower numbers are evaluated first.
conditionobjectCondition edges onlyThe condition to evaluate

Edge Kinds

KindDescription
defaultUnconditional transition — used when a node completes normally
conditionEvaluated in order; first matching condition wins
elseFallback when no condition edges match (required on logic_split nodes)
skipUsed with skipResponse conversation nodes — immediate transition without waiting for user input

Conditions on Any Node

You can attach condition edges to any node type. This is particularly useful on conversation nodes — branch directly based on the caller’s response without needing a separate logic split node. For example, a greeting node that routes callers to different paths:
[
  {
    "id": "e1",
    "source": "greeting",
    "target": "collect_info",
    "kind": "condition",
    "order": 0,
    "condition": {
      "type": "prompt",
      "promptText": "The caller wants help and didn't mention billing or tech support"
    }
  },
  {
    "id": "e2",
    "source": "greeting",
    "target": "billing_confirm",
    "kind": "condition",
    "order": 1,
    "condition": {
      "type": "prompt",
      "promptText": "The caller needs billing help"
    }
  },
  {
    "id": "e3",
    "source": "greeting",
    "target": "tech_confirm",
    "kind": "condition",
    "order": 2,
    "condition": {
      "type": "prompt",
      "promptText": "The caller needs technical help"
    }
  }
]
If no condition matches, the agent stays at the greeting node and continues the conversation until a condition is met.

Conditions

Conditions determine whether a condition edge is followed. There are two types:

Prompt Conditions

The LLM evaluates a natural language question against the conversation context.
{
  "type": "prompt",
  "promptText": "Did the caller agree to schedule an appointment?"
}
FieldTypeDescription
type"prompt"Prompt-based condition
promptTextstringA yes/no question the LLM evaluates against the conversation

Equation Conditions

Variable-based conditions that compare flow variables against values. No LLM call required.
{
  "type": "equation",
  "match": "all",
  "equations": [
    { "variable": "order_status", "operator": "==", "value": "shipped" },
    { "variable": "delivery_eta", "operator": "exists" }
  ]
}
FieldTypeDescription
type"equation"Equation-based condition
match"any" or "all"Whether any or all equations must be true. Optional — defaults to "all".
equationsarrayList of equations to evaluate
Each equation:
FieldTypeDescription
variablestringFlow variable name to compare
operatorstringComparison operator
valuestringValue to compare against (not required for exists/not_exists)
Available operators: ==, !=, contains, not_contains, contained_in, not_contained_in, >, <, >=, <=, exists, not_exists

Global Nodes and Edges

Global nodes can be triggered from any point in the conversation, not just from a specific predecessor node. This is useful for handling requests that can happen at any time, such as “transfer me to a human” or “I want to cancel.” To make a node global:
  1. Set isGlobal: true on the node
  2. Add one or more edges with source: "__global__" targeting that node
  3. Global edges must have a condition — typically a prompt condition
{
  "id": "global_transfer_edge",
  "source": "__global__",
  "target": "transfer_human",
  "kind": "condition",
  "order": 0,
  "condition": {
    "type": "prompt",
    "promptText": "Is the caller asking to speak with a human or a real person?"
  }
}

Transition Behavior

After each user reply, the agent evaluates edges in this priority order:
  1. Global edges — checked first, across all global nodes. If a global condition matches, the flow jumps to that global node regardless of where the conversation currently is.
  2. Condition edges on the current node — evaluated in order, first match wins
  3. Else edge — taken if no condition edges matched
  4. Default edge — unconditional, taken if present
  5. No match — the agent stays in the current node and continues the conversation
This means a conversation node doesn’t need a fallback edge. If no conditions match, the agent simply keeps talking at the current step — useful for nodes that need to collect information before moving on (e.g. “keep asking until the caller provides their phone number”).

Validation Rules

When submitting a flow via the API, the following rules are enforced:
  1. schemaVersion must be 1
  2. Every node must have a unique id
  3. begin.startNodeId must reference an existing node
  4. All edge source and target values must reference existing nodes (except "__global__" as source)
  5. logic_split nodes must have exactly one else edge
  6. skipResponse conversation nodes must have exactly one skip edge and no other outgoing edges
  7. Global nodes (isGlobal: true) must have at least one __global__ edge targeting them
  8. __global__ edges must target nodes with isGlobal: true
  9. condition edges must have an order value, unique per source node
  10. Prompt conditions must have non-empty promptText; equation conditions must have at least one equation
  11. Total flow size must not exceed 48 KB

Complete Example

Here is a complete flow for an order status hotline:
{
  "schemaVersion": 1,
  "begin": {
    "startNodeId": "greeting",
    "whoSpeaksFirst": "agent"
  },
  "nodes": [
    {
      "id": "greeting",
      "type": "conversation",
      "name": "Greeting",
      "position": { "x": 300, "y": 100 },
      "data": {
        "instructionType": "prompt",
        "instruction": "Greet the caller warmly. Ask for their order number so you can look up the status."
      }
    },
    {
      "id": "extract_order",
      "type": "extract_variable",
      "name": "Get Order Number",
      "position": { "x": 300, "y": 300 },
      "data": {
        "variables": [
          {
            "variableName": "order_number",
            "description": "The customer's order number",
            "variableType": "text"
          }
        ]
      }
    },
    {
      "id": "lookup",
      "type": "function",
      "name": "Look Up Order",
      "position": { "x": 300, "y": 500 },
      "data": {
        "toolName": "lookup_order",
        "speakDuringExecution": true,
        "speakInstruction": "One moment while I look that up.",
        "speakInstructionType": "static",
        "outputVariables": [
          { "outputKey": "status", "variableName": "order_status" }
        ]
      }
    },
    {
      "id": "check_status",
      "type": "logic_split",
      "name": "Check Status",
      "position": { "x": 300, "y": 700 },
      "data": {}
    },
    {
      "id": "shipped_response",
      "type": "conversation",
      "name": "Order Shipped",
      "position": { "x": 100, "y": 900 },
      "data": {
        "instructionType": "prompt",
        "instruction": "Tell the customer their order has been shipped and ask if there's anything else you can help with."
      }
    },
    {
      "id": "pending_response",
      "type": "conversation",
      "name": "Order Pending",
      "position": { "x": 500, "y": 900 },
      "data": {
        "instructionType": "prompt",
        "instruction": "Let the customer know their order is still being processed and should ship within 1-2 business days. Ask if they have any other questions."
      }
    },
    {
      "id": "transfer_support",
      "type": "call_transfer",
      "name": "Transfer to Support",
      "isGlobal": true,
      "position": { "x": 700, "y": 500 },
      "data": {
        "transferTo": "+12025559999",
        "transferMode": "cold",
        "speakDuringExecution": true,
        "speakInstruction": "Let me connect you with our support team right away."
      }
    },
    {
      "id": "goodbye",
      "type": "end",
      "name": "End Call",
      "position": { "x": 300, "y": 1100 },
      "data": {
        "message": "Thank you for calling! Goodbye."
      }
    }
  ],
  "edges": [
    {
      "id": "e1",
      "source": "greeting",
      "target": "extract_order",
      "kind": "default"
    },
    {
      "id": "e2",
      "source": "extract_order",
      "target": "lookup",
      "kind": "default"
    },
    {
      "id": "e3",
      "source": "lookup",
      "target": "check_status",
      "kind": "default"
    },
    {
      "id": "e4",
      "source": "check_status",
      "target": "shipped_response",
      "kind": "condition",
      "order": 0,
      "condition": {
        "type": "equation",
        "match": "all",
        "equations": [
          { "variable": "order_status", "operator": "==", "value": "shipped" }
        ]
      }
    },
    {
      "id": "e5",
      "source": "check_status",
      "target": "pending_response",
      "kind": "else"
    },
    {
      "id": "e6",
      "source": "shipped_response",
      "target": "goodbye",
      "kind": "default"
    },
    {
      "id": "e7",
      "source": "pending_response",
      "target": "goodbye",
      "kind": "default"
    },
    {
      "id": "e8",
      "source": "__global__",
      "target": "transfer_support",
      "kind": "condition",
      "order": 0,
      "condition": {
        "type": "prompt",
        "promptText": "Is the caller asking to speak with a human representative?"
      }
    }
  ]
}
This flow:
  1. Greets the caller and asks for their order number
  2. Extracts the order number from the conversation
  3. Calls a tool to look up the order status
  4. Branches based on whether the status is "shipped" or anything else
  5. Responds with the appropriate message
  6. Ends the call
  7. At any point, if the caller asks for a human, the call is transferred (global node)

Importing Flows via API

Agent mode is set at creation time and cannot be changed afterwards. To use conversation flows, create an agent with mode: "conversation_flow".

Creating a flow agent

Use the Create Agent endpoint with mode set to "conversation_flow" and the flowDefinition field set to your flow JSON:
curl -X POST https://api.revring.ai/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Status Agent",
    "mode": "conversation_flow",
    "promptTemplate": "You are a helpful order status assistant.",
    "flowDefinition": {
      "schemaVersion": 1,
      "begin": { ... },
      "nodes": [ ... ],
      "edges": [ ... ]
    }
  }'

Updating a flow

To update the flow on an existing conversation_flow agent, use the Update Agent endpoint with just the flowDefinition field:
curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flowDefinition": {
      "schemaVersion": 1,
      "begin": { ... },
      "nodes": [ ... ],
      "edges": [ ... ]
    }
  }'
mode is immutable after creation. You cannot convert a single_prompt agent to conversation_flow — create a new agent instead.
You can generate flow definition JSON with AI tools like ChatGPT. Share this reference page as context, describe the conversation flow you want, and paste the generated JSON into the API call or the dashboard’s import feature.

Using Variables in Flows

Flow nodes support {{variable}} syntax in instruction text. Variables can come from:
  • System variables ({{current_time}}, {{user_number}}, etc.) — see Prompting & Variables
  • Default variables configured on your agent
  • Dynamic variables passed via the API or pre-call webhook
  • Extracted variables from extract_variable nodes
  • Tool output variables mapped via outputVariables in function nodes
All variables are available to every node in the flow once set.