IVR API
Create programmable IVR menus with TTS audio generation and DID assignment.
IVR (Interactive Voice Response) trees define the menu structure for inbound calls. Each IVR is a set of nodes — gather nodes present a menu and wait for DTMF input, and action nodes perform a terminal operation like transfer or hangup.
IVR definitions are cached on write. At call time, the handler reads the cached definition directly — no runtime fetch to the hub.
Create IVR
POST /ivr
curl -X POST https://api.trunx.io/ivr \
-H "Authorization: Bearer tk_live_..." \
-H "Content-Type: application/json" \
-d '{
"id": "qualify-business",
"nodes": {
"welcome": {
"audio": "welcome.wav",
"gather": { "1": "has_business", "9": "dnc" },
"timeout_node": "welcome"
},
"has_business": {
"audio": "business_check.wav",
"gather": { "1": "transfer_ai", "2": "unqualified" }
},
"transfer_ai": { "action": "transfer" },
"unqualified": { "action": "hangup", "audio": "not_qualified.wav" },
"dnc": { "action": "suppress" }
}
}'const res = await fetch("https://api.trunx.io/ivr", {
method: "POST",
headers: {
"Authorization": "Bearer tk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
id: "qualify-business",
nodes: {
welcome: {
audio: "welcome.wav",
gather: { "1": "has_business", "9": "dnc" },
timeout_node: "welcome",
},
has_business: {
audio: "business_check.wav",
gather: { "1": "transfer_ai", "2": "unqualified" },
},
transfer_ai: { action: "transfer" },
unqualified: { action: "hangup", audio: "not_qualified.wav" },
dnc: { action: "suppress" },
},
}),
});
const data = await res.json();import requests
res = requests.post(
"https://api.trunx.io/ivr",
headers={"Authorization": "Bearer tk_live_..."},
json={
"id": "qualify-business",
"nodes": {
"welcome": {
"audio": "welcome.wav",
"gather": {"1": "has_business", "9": "dnc"},
"timeout_node": "welcome",
},
"has_business": {
"audio": "business_check.wav",
"gather": {"1": "transfer_ai", "2": "unqualified"},
},
"transfer_ai": {"action": "transfer"},
"unqualified": {"action": "hangup", "audio": "not_qualified.wav"},
"dnc": {"action": "suppress"},
},
},
)
data = res.json()Node types
Gather node
Plays audio and waits for DTMF input. Routes to the next node based on the digit pressed.
| Field | Type | Required | Description |
|---|---|---|---|
audio | string | Yes | Audio file to play (WAV/MP3 filename or TTS-generated ID) |
gather | object | Yes | Map of DTMF digits to target node names |
timeout_node | string | No | Node to jump to if no input received (defaults to replaying current node) |
Action node
Performs a terminal operation and ends the call flow.
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | One of "transfer", "hangup", "suppress" |
audio | string | No | Optional audio to play before executing the action |
Actions
| Action | Description |
|---|---|
transfer | Connect the caller to an AI voice agent |
hangup | End the call (optionally play goodbye audio first) |
suppress | Add the caller's number to the DNC suppression list and hang up |
List IVRs
GET /ivr
Returns all IVR definitions for the authenticated account.
Get IVR
GET /ivr/:id
Returns a specific IVR definition with all nodes.
Update IVR
PATCH /ivr/:id
Update an existing IVR definition. The updated definition is immediately written to the Redis cache.
IVR updates take effect immediately for new calls. Calls already in progress continue using the IVR definition that was active when the call started.
Delete IVR
DELETE /ivr/:id
Delete an IVR definition. Fails if the IVR is currently assigned to a DID.
Generate audio
POST /ivr/:id/audio
Generate TTS audio for a specific IVR node. The generated audio file is stored and associated with the node.
curl -X POST "https://api.trunx.io/ivr/qualify-business/audio" \
-H "Authorization: Bearer tk_live_..." \
-H "Content-Type: application/json" \
-d '{
"node": "welcome",
"text": "Thank you for calling. Press 1 if you are a business owner. Press 9 to be added to our do not call list.",
"voice": "rachel"
}'const res = await fetch("https://api.trunx.io/ivr/qualify-business/audio", {
method: "POST",
headers: {
"Authorization": "Bearer tk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
node: "welcome",
text: "Thank you for calling. Press 1 if you are a business owner. Press 9 to be added to our do not call list.",
voice: "rachel",
}),
});
const data = await res.json();import requests
res = requests.post(
"https://api.trunx.io/ivr/qualify-business/audio",
headers={"Authorization": "Bearer tk_live_..."},
json={
"node": "welcome",
"text": "Thank you for calling. Press 1 if you are a business owner. Press 9 to be added to our do not call list.",
"voice": "rachel",
},
)
data = res.json()Assign IVR to DID
POST /ivr/:id/assign
Bind an IVR definition to a phone number. Inbound calls to that DID will be handled by this IVR tree.
curl -X POST "https://api.trunx.io/ivr/qualify-business/assign" \
-H "Authorization: Bearer tk_live_..." \
-H "Content-Type: application/json" \
-d '{ "did": "+19495551234" }'await fetch("https://api.trunx.io/ivr/qualify-business/assign", {
method: "POST",
headers: {
"Authorization": "Bearer tk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({ did: "+19495551234" }),
});import requests
requests.post(
"https://api.trunx.io/ivr/qualify-business/assign",
headers={"Authorization": "Bearer tk_live_..."},
json={"did": "+19495551234"},
)Each DID can only have one IVR assigned at a time. Assigning a new IVR to a DID replaces the previous assignment.