Skip to main content

Dial Plans

Build visual call routing flows with dial plans.

Overview​

A dial plan defines how incoming calls are routed through a series of steps, represented as a flowchart. Each step in the flowchart is called a node. Nodes check conditions (like business hours), ring people, play audio, or hand the call to an application, then route to the next step based on the outcome.

Think of it like a flowchart you'd draw on a whiteboard:

Text
┌───────────┐
│ Start │
└─────┬─────┘
│
▼
┌─────────────┐ open ┌───────────────┐
│ Check Hours │────────────▶│ Reception │
│ [Schedule] │ │ [InternalDial]│
└──────┬──────┘ └───────┬───────┘
│ │
│ closed timeout
│ │
└───────────┬────────────────┘
│
▼
┌───────────┐
│ Voicemail │
└───────────┘

In this example:

  1. Every call starts at the "Check Hours" schedule node
  2. If the schedule is open, the call rings reception
  3. If reception doesn't answer (timeout), or if the schedule is closed, the call routes to voicemail

Creating a Dial Plan​

JavaScript
const dialPlan = await dialstack.dialPlans.create(
{
name: 'Main Line Routing',
entry_node: 'check_hours',
nodes: [
{
id: 'check_hours',
type: 'schedule',
config: {
schedule: 'sched_01h2xcejqtf2nbrexx3vqjhp50',
open: 'reception',
closed: 'voicemail',
},
},
{
id: 'reception',
type: 'internal_dial',
config: {
target: 'user_01h2xcejqtf2nbrexx3vqjhp45',
timeout: 30,
next: 'voicemail',
},
},
{
id: 'voicemail',
type: 'internal_dial',
config: {
target: 'user_01h2xcejqtf2nbrexx3vqjhp45',
timeout: 0, // straight to the user's voicemail
},
},
],
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Key Concepts​

Entry Node​

The entry_node specifies which node executes first. Think of it as where the "Start" arrow points in your flowchart.

Exit Targets​

Each node's exits are fields in its config that name the next node (open/closed on a schedule node, next on most others). Omit an exit to end the call at that point, unless the node type says otherwise below.

Node IDs​

Node IDs are strings you define (like "check_hours" or "reception"). They must be unique within a dial plan and are used to wire nodes together.

Visual Position​

Nodes can include optional position coordinates (x, y) for rendering in a visual editor. If omitted, nodes are auto-positioned.

Drafts​

A dial plan can be saved before it is finished: the ID a node references (a schedule, target, clip, phone number or voice app) may be left empty. At call time, a node with an empty reference is skipped to its next exit, or ends the call when it has none. A menu with no prompt clip waits for a digit without playing anything.

Node Types​

Every node has an id, a type, an optional position, and a config whose shape depends on the type. The full schemas are in the API reference.

TypeWhat it doesExits
scheduleBranches on whether a schedule is openopen, closed, holiday
internal_dialRings a user, ring group, call queue, or nested plannext
ring_all_usersRings every user in the account at oncenext
external_dialDials an outside phone numbernext
menuPlays a prompt and routes on the digit the caller pressesper-digit next_node, timeout_next_node, invalid_next_node
audio_clipPlays an audio clipnext
voice_appHands the call to, or notifies, a voice appnext (notify mode)
hang_upEnds the callnone

schedule​

Routes the call based on whether a schedule is currently open, closed, or on a holiday.

FieldTypeDescription
schedulestringID of the schedule to check
openstringNode to route to while the schedule is open
closedstringNode to route to while it is closed. Holidays route here too unless holiday is set
holidaystringOptional. Node to route to on a holiday, when you want holidays handled apart from closed hours

internal_dial​

Rings an internal target. If the call is answered, the dial plan ends; on no answer or busy, the call follows next.

FieldTypeDescription
targetstringID of a user, ring group, call queue, nested dial plan, or shared voicemail box
timeoutintegerRequired. Ring time in seconds, 0–300. 0 skips the target and continues at next
timeout_overridebooleanMakes timeout govern a target that has its own timing (a ring group, a call queue, a user with Find Me / Follow Me). Defaults to false. Can't be combined with timeout: 0
nextstringNode to route to on no answer or busy

The target decides how some of these behave:

  • User: with no next, an unanswered or skipped call goes to the user's voicemail.
  • Ring group: with no next, an unanswered call runs the group's own timeout action; a skipped one (timeout: 0) ends.
  • Shared voicemail box: the call goes straight to voicemail and the plan ends; timeout and next are not used.
  • Call queue: next is the queue's timeout exit, taken when the caller's maximum wait elapses or the queue refuses them. Leave it unset to run the queue's own timeout action instead; a skipped queue (timeout: 0) with no next ends the call.
  • Nested dial plan: the nested plan takes over the call; timeout, timeout_override and next are not used.

target replaces the older target_id key, which is still accepted. A voice app is also still accepted as a target, but new plans should use the voice_app node instead.

ring_all_users​

Rings every user in the account at the same time and connects the first to answer.

FieldTypeDescription
timeoutintegerRequired. Ring time in seconds, 1–300
nextstringNode to route to when nobody answers. With none, the call goes to the voicemail of the user with the lowest extension

external_dial​

Dials an outside phone number. If the call is answered, the dial plan ends.

FieldTypeDescription
phone_numberstringThe number to dial, in E.164 format
timeoutintegerRequired. Ring time in seconds, 1–120
nextstringNode to route to on no answer or busy

Plays a prompt (an auto attendant, or IVR) and routes on the digit the caller presses.

FieldTypeDescription
prompt_clipstringID of the audio clip to play
timeoutintegerRequired. Seconds to wait for a digit after the prompt, 1–30
optionsarrayUp to 12 entries, each with a digit (0–9, * or #), the next_node to route to, and an optional label for the editor
timeout_next_nodestringNode to route to when no digit is pressed
invalid_next_nodestringNode to route to when a digit with no option is pressed
extension_entry_enabledbooleanLets callers dial any extension in the account from this menu, as well as the options. Defaults to false

Unlike other nodes, a menu exit left empty replays the prompt rather than ending the call (or ends it, when the menu has no prompt clip). Enabling extension_entry_enabled makes every extension in the account reachable by anyone who can reach this dial plan.

audio_clip​

Plays an audio clip, then routes to the next node.

FieldTypeDescription
clipstringID of the audio clip to play
nextstringNode to route to after the clip

The older type name sound_clip is still accepted with the same config.

voice_app​

Hands the call to a voice app, or notifies one as the call passes through.

FieldTypeDescription
voice_appstringID of the voice app
modestringcontrol (default): the voice app receives call.received and takes over the call; the plan ends here. notify: the voice app receives call.notify and the call continues to next
nextstringIn notify mode, the node the call continues to

notify mode can't reference the voice app an AI agent manages: saving the plan returns 422.

hang_up​

Ends the call. Its config is an empty object, {}.

Examples​

After-Hours Voicemail​

Route calls to voicemail when closed:

Text
Start → Check Hours [Schedule] → (open) → Sales [Internal Dial → ring group]
→ (closed/holiday) → Voicemail
JSON
{
"name": "After-Hours Voicemail",
"entry_node": "check_hours",
"nodes": [
{
"id": "check_hours",
"type": "schedule",
"config": {
"schedule": "sched_01h2xcejqtf2nbrexx3vqjhp50",
"open": "sales",
"closed": "voicemail"
}
},
{
"id": "sales",
"type": "internal_dial",
"config": {
"target": "rg_01h2xcejqtf2nbrexx3vqjhp51",
"timeout": 30,
"next": "voicemail"
}
},
{
"id": "voicemail",
"type": "internal_dial",
"config": {
"target": "svm_01h2xcejqtf2nbrexx3vqjhp53",
"timeout": 0
}
}
]
}

Multi-Level Routing​

Chain schedule checks, here an on-call line ahead of business hours:

Text
Start → Check On-Call [Schedule] → (open) → On-Call [Internal Dial]
→ (closed/holiday) → Check Hours [Schedule] → (open) → Reception [Internal Dial]
→ (closed/holiday) → Voicemail
JSON
{
"name": "Multi-Level Routing",
"entry_node": "check_on_call",
"nodes": [
{
"id": "check_on_call",
"type": "schedule",
"config": { "schedule": "ON_CALL_SCHEDULE_ID", "open": "on_call", "closed": "check_hours" }
},
{
"id": "on_call",
"type": "internal_dial",
"config": { "target": "ON_CALL_USER_ID", "timeout": 30, "next": "voicemail" }
},
{
"id": "check_hours",
"type": "schedule",
"config": {
"schedule": "BUSINESS_HOURS_SCHEDULE_ID",
"open": "reception",
"closed": "voicemail"
}
},
{
"id": "reception",
"type": "internal_dial",
"config": { "target": "RECEPTION_USER_ID", "timeout": 30, "next": "voicemail" }
},
{
"id": "voicemail",
"type": "internal_dial",
"config": { "target": "SHARED_VOICEMAIL_BOX_ID", "timeout": 0 }
}
]
}

Greet the caller and let them choose:

Text
Start → Main Menu [Menu] → (1) → Sales [Internal Dial]
→ (2) → Support [Internal Dial → call queue]
→ (no input) → Goodbye [Audio Clip] → Hang Up
JSON
{
"name": "Main Menu",
"entry_node": "main_menu",
"nodes": [
{
"id": "main_menu",
"type": "menu",
"config": {
"prompt_clip": "aud_01h2xcejqtf2nbrexx3vqjhp54",
"timeout": 5,
"options": [
{ "digit": "1", "next_node": "sales", "label": "Sales" },
{ "digit": "2", "next_node": "support", "label": "Support" }
],
"timeout_next_node": "goodbye"
}
},
{
"id": "sales",
"type": "internal_dial",
"config": { "target": "rg_01h2xcejqtf2nbrexx3vqjhp51", "timeout": 30 }
},
{
"id": "support",
"type": "internal_dial",
"config": { "target": "qu_01h2xcejqtf2nbrexx3vqjhp61", "timeout": 30 }
},
{
"id": "goodbye",
"type": "audio_clip",
"config": { "clip": "aud_01h2xcejqtf2nbrexx3vqjhp55", "next": "end" }
},
{ "id": "end", "type": "hang_up", "config": {} }
]
}

Fallback Chain​

Try multiple users, then a cell phone, before voicemail:

Text
Start → Reception [Internal Dial] → (timeout 30s) → Manager [Internal Dial] → (timeout 20s) → Cell [External Dial] → Voicemail
JSON
{
"name": "Fallback Chain",
"entry_node": "reception",
"nodes": [
{
"id": "reception",
"type": "internal_dial",
"config": {
"target": "user_01h2xcejqtf2nbrexx3vqjhp45",
"timeout": 30,
"next": "manager"
}
},
{
"id": "manager",
"type": "internal_dial",
"config": {
"target": "user_01h2xcejqtf2nbrexx3vqjhp46",
"timeout": 20,
"next": "cell"
}
},
{
"id": "cell",
"type": "external_dial",
"config": {
"phone_number": "+14155551234",
"timeout": 20,
"next": "voicemail"
}
},
{
"id": "voicemail",
"type": "internal_dial",
"config": {
"target": "user_01h2xcejqtf2nbrexx3vqjhp45",
"timeout": 0
}
}
]
}

Transcribe, then route​

Notify a voice app so it can listen to the call, then ring everyone:

JSON
{
"name": "Transcribed Main Line",
"entry_node": "transcribe",
"nodes": [
{
"id": "transcribe",
"type": "voice_app",
"config": {
"voice_app": "va_01h2xcejqtf2nbrexx3vqjhp49",
"mode": "notify",
"next": "everyone"
}
},
{
"id": "everyone",
"type": "ring_all_users",
"config": { "timeout": 24 }
}
]
}

API Reference​

  • Dial Plans — Create, update, and delete dial plans
  • Schedules — Define business hours for schedule nodes