The flows API manages IVR Studio call flows: create and update a flow's nodes and edges (flow_data), publish it after validation, preview the VoiceML it produces, place a test call, and assign phone numbers to it. It is the same API the builder uses, so a flow saved in the builder can be read here and the other way round.
Endpoints#
| Method | Path | Notes |
|---|---|---|
| GET | /v1/ivr/flows |
List flows. Query: status (draft, published), page, per_page (default 20) |
| POST | /v1/ivr/flows |
Create a draft flow → 201 |
| GET | /v1/ivr/flows/{id} |
Get a flow |
| PUT | /v1/ivr/flows/{id} |
Update name, description or flow_data |
| DELETE | /v1/ivr/flows/{id} |
Delete. Returns 409 flow_in_use while a number or automation uses it |
| POST | /v1/ivr/flows/{id}/publish |
Validate and publish |
| GET | /v1/ivr/flows/{id}/preview |
VoiceML preview: { "data": { "instructions": [ ... ] } }. Add ?variables=latest to fill tokens with the last call's variables |
| POST | /v1/ivr/flows/{id}/test-call |
Call your phone with the saved flow |
Create a flow#
curl -X POST https://voice-api.edesy.in/v1/ivr/flows \
-H "Authorization: Bearer $EDESY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Main menu",
"description": "Sales or support",
"flow_data": {
"nodes": [
{ "id": "start-1", "type": "start", "position": { "x": 0, "y": 0 }, "data": {} },
{ "id": "menu-1", "type": "menu", "position": { "x": 0, "y": 120 },
"data": { "prompt": "Press 1 for Sales, 2 for Support.", "timeout": 5, "max_digits": 1,
"options": { "1": "Sales", "2": "Support" } } },
{ "id": "transfer-1", "type": "transfer", "position": { "x": -150, "y": 260 },
"data": { "number": "+919800000001", "timeout": 30 } },
{ "id": "transfer-2", "type": "transfer", "position": { "x": 150, "y": 260 },
"data": { "number": "+919800000002", "timeout": 30 } }
],
"edges": [
{ "id": "e1", "source": "start-1", "target": "menu-1" },
{ "id": "e2", "source": "menu-1", "target": "transfer-1", "sourceHandle": "digit-1" },
{ "id": "e3", "source": "menu-1", "target": "transfer-2", "sourceHandle": "digit-2" }
]
}
}'
| Field | Type | Notes |
|---|---|---|
name |
string | Required, 1 to 255 characters |
description |
string | Optional, up to 1000 characters |
flow_data |
object | nodes and edges, as below |
flow_data#
| Part | Fields |
|---|---|
| Node | id, type (start, config, say, play, menu, ask, transfer, dial, voicemail, email, pause, hangup, http_request, order_action, stream, reject), position {x, y}, data (the node's settings) |
| Edge | id, source, target, sourceHandle (for nodes with several exits) |
Exit handles: Menu digit-<key> and timeout; Ask answered and no_answer; HTTP Request (sync) and Order Action (wait) success and failure. Node data keys match the settings on each node page.
The flow object returned has id, name, description, flow_data, status (draft or published), version, created_at, updated_at, published_at and usage (how many numbers and automations use it).
Publish#
POST/v1/ivr/flows/{id}/publish validates the flow. If it passes, it returns { "data": { "status": "published" } }. If not, it returns 400:
{
"error": {
"code": "flow_invalid",
"message": "The flow has problems that must be fixed before publishing",
"details": [
{ "node_id": "say-1", "severity": "error", "message": "Say node has no text." }
]
}
}
Test call#
{ "phone_number": "9800000000", "variables": { "order_id": "A123" } }
Returns { "data": { "call_sid": "...", "status": "..." } }. Errors: 400 invalid_phone (must be a 10-digit Indian number), 400 validation_error (bad variables), 402 insufficient_balance.
Assign numbers#
| Method | Path | Body or notes |
|---|---|---|
| POST | /v1/ivr/numbers |
{ "phone_number": "+919800000000", "flow_id": "<uuid>" } |
| GET | /v1/ivr/numbers |
Lists assignments with phone_number, flow_id, flow_name, is_active |
| DELETE | /v1/ivr/numbers/{id} |
Removes an assignment |
Upload audio#
POST/v1/ivr/audio (multipart file upload) stores an MP3, WAV, M4A or OGG file of up to 5 MB and returns the value to use as a Play node's url.