POST/v1/ivr/calls calls an Indian phone number and runs one of your published IVR flows on the call. Pass per-call variables (used as {{var.name}}), your own reference_id, and a status_callback_url that receives the signed result when the call ends. An Idempotency-Key header makes retries safe. DNC and 09:00-21:00 IST calling hours are enforced.
Request body#
| Field | Type | Required | Notes |
|---|---|---|---|
flow_id |
string (UUID) | Required | The flow must be published |
to |
string | Required | Indian number: 9800000000, 919800000000 or +919800000000 |
from |
string | Optional | One of your numbers. Default: your first number assigned to IVR |
variables |
object | Optional | Strings, numbers or booleans. Used as {{var.<name>}}. See limits |
reference_id |
string | Optional | Your own ID, up to 128 characters. Echoed in callbacks and available as {{reference_id}} |
status_callback_url |
string | Optional | https:// only, up to 2048 characters. Receives the call result |
ring_timeout_sec |
integer | Optional | 10 to 60. Default 45 |
Headers#
| Header | Notes |
|---|---|
Authorization: Bearer sk_... or X-API-Key: sk_... |
Required |
Idempotency-Key |
Optional, up to 128 characters. See idempotency |
Content-Type: application/json |
Required |
Example#
curl -X POST https://voice-api.edesy.in/v1/ivr/calls \
-H "Authorization: Bearer $EDESY_API_KEY" \
-H "Idempotency-Key: order-A123-confirm" \
-H "Content-Type: application/json" \
-d '{
"flow_id": "5b1c2f0e-0000-4000-8000-000000000000",
"to": "+919800000000",
"variables": { "order_id": "A123", "name": "Ravi", "amount": 499 },
"reference_id": "crm-lead-42",
"status_callback_url": "https://crm.example.com/ivr/status"
}'
{
"data": {
"call_sid": "a1b2c3d4-0000-4000-8000-000000000000",
"call_log_id": "7f3e2d1c-0000-4000-8000-000000000000",
"status": "queued",
"reference_id": "crm-lead-42"
}
}
How it works#
- Edesy checks the flow is published, the number is valid and not on your do-not-call list, the time is within calling hours, your wallet has balance, and you are under the concurrency limit.
- The call is queued and dialled from
from(or your default IVR number). - When the person answers, the flow runs with your
variablesfilled in. - HTTP Request nodes in the flow call your API as the caller presses keys.
- When the call ends, Edesy POSTs the result to
status_callback_url.
Idempotency#
Send Idempotency-Key: <unique string> to make retries safe:
- The same key and body within 24 hours returns the original response and does not dial again.
- The same key with a different body returns
422 idempotency_key_reused. - A repeat while the first request is still being processed returns
409 idempotency_key_in_use.
Errors#
| HTTP | code |
Meaning |
|---|---|---|
| 400 | invalid_request |
Body is not valid JSON |
| 400 | validation_error |
Bad field, variables, callback URL or Idempotency-Key |
| 400 | invalid_phone |
to is not a valid Indian number |
| 400 | no_caller_id |
No from and no number assigned to IVR |
| 402 | insufficient_balance |
Top up your wallet |
| 403 | from_not_owned |
from is not one of your numbers |
| 404 | flow_not_found |
No such flow in this workspace |
| 409 | flow_not_published |
Publish the flow first |
| 409 | idempotency_key_in_use |
The same key is still being processed |
| 422 | idempotency_key_reused |
Same key, different body |
| 422 | dnc_suppressed |
The number is on your do-not-call list |
| 422 | outside_calling_hours |
Outside 09:00-21:00 IST |
| 429 | concurrency_limit |
Too many of your API calls are in progress |
| 502 | carrier_error |
The carrier rejected the call |
Get a call#
Returns the call log for any IVR call (API, inbound, test or order automation): status, duration, price, dtmf_path, variables, reference_id, call_outcome, and http_requests (every HTTP request the flow made during the call). Secret-looking header values are masked. Unknown calls return 404 not_found.
To list API calls, use GET/v1/ivr/call-logs?source=api&reference_id=crm-lead-42. See Logs API.
Status callback#
When the call ends, Edesy sends POST to your status_callback_url. It is signed like HTTP Request nodes and makes up to 5 attempts, with backoff, if your endpoint fails.
{
"event": "ivr.call.completed",
"timestamp": "2026-10-28T10:15:00Z",
"data": {
"call_sid": "a1b2c3d4-0000-4000-8000-000000000000",
"call_log_id": "7f3e2d1c-0000-4000-8000-000000000000",
"reference_id": "crm-lead-42",
"flow_id": "5b1c2f0e-0000-4000-8000-000000000000",
"flow_version": 3,
"from": "918000000000",
"to": "919800000000",
"status": "completed",
"hangup_cause": "normal",
"sip_status": 0,
"call_outcome": "answered",
"duration_sec": 42,
"price": 1.5,
"currency": "INR",
"dtmf_path": ["1"],
"variables": { "order_id": "A123" },
"http_requests": [ { "node_id": "http-1", "status": "delivered", "response_code": 200 } ],
"answers": [ { "question_key": "city", "question": "Which city are you calling from?", "transcript": "Lucknow", "status": "captured", "attempt": 1 } ]
}
}
| Field | Values |
|---|---|
event |
ivr.call.completed, ivr.call.no-answer, ivr.call.busy, ivr.call.canceled, ivr.call.failed |
hangup_cause |
normal for an answered call. Otherwise the reason: busy, rejected, no_answer, unavailable, invalid_number, call_blocked, network_timeout, carrier_error, caller_canceled, call_failed |
sip_status |
The SIP response code for a call that did not connect. A 480 (switched off) is reported as no-answer, a 603 as busy |
call_outcome |
answered, callee, unreachable, invalid_number, network (usually temporary; retry later), account or platform (Edesy could not place it) |
answers |
Spoken answers from Ask nodes |
Status callbacks are listed in HTTP Logs with the kind Status callback.
Limits#
| Limit | Value |
|---|---|
| Variables per call | 20 keys. Name [a-zA-Z_][a-zA-Z0-9_]{0,63}, value up to 512 characters, 4 KB in total |
| Concurrent API calls | 10 per workspace by default. Ask support to raise it |
| Calling hours | 09:00-21:00 IST |
| Do-not-call list | Numbers on your DNC list are refused with dnc_suppressed |
| Ring timeout | 10 to 60 seconds, default 45 |
Pricing#
Rs 1.50 per minute, billed per started minute from your wallet. Unanswered calls are free. Prices exclude 18% GST. See Pricing.