Voice v2 APIs work only when your users are added as co-workers in the Exotel dashboard and are using Exotel dashboard for calling. These APIs will not be enhanced going forward hence please use the latest available Voice v3 APIs for your integration purposes.
CCM Make Call API will only allow calls to an agent if they are added as a user on Exotel and are available (device is ON and they are not busy on another call). This API would also mark the agent as busy to prevent them to make/receive any other call and incoming calls would be routed to other agents if available.
To make a call connecting agent to customer, you will need to make a HTTP POST request to
https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls
Example (For Singapore Stamp)-
https://<your_api_key>:<your_api_token>@ccm-api.exotel.com/v2/accounts/<your_sid>/calls
Example (For Mumbai Stamp)
https://<your_api_key>:<your_api_token>@ccm-api.in.exotel.com/v2/accounts/<your_sid>/calls
<your_api_key>
and <your_api_token>
with the API key and token created by you OR use Basic Authentication.<your_sid>
with your “Account sid”<subdomain>
with the region of your account<user_id>
with the user's unique identifier and device identifier as present in GET user details API.<your_api_key>
, <your_api_token>
and <your_sid>
are available in the API settings page of your Exotel Dashboard
The following are the POST parameters supported with Content-Type: application/json
set in the header
Parameter Name |
Mandatory / Optional |
Value |
from |
Mandatory |
JSON block indicating the number to be dialed first (leg1). API will allow the call to made only if the user device is available (turned ON) and free (not busy on another call). Possible inputs-
|
to |
Mandatory |
JSON block containing the phone number of the customer (leg2) to be connected to agent.
Example - |
virtual_number |
Mandatory |
ExoPhone (Virtual Number) present in your account in E.164 format or local format for Indian virtual numbers. This will be the number that will be used to make the call to both agent (leg1) and customer (leg2). Example - "virtual_number": "+911454XXXX" |
recording |
Optional |
Boolean; Record the conversation of your call. The RecordingUrl will be sent to the StatusCallback URL if this is set to 'true' and the call conversation happens. Can be:
|
recording_channels |
Optional |
String; Number of audio channels to be present in the final recording. This parameter works if
|
wait_audio_url |
Optional |
String; Play this audio to the caller. When the first leg (From) has to wait for the other leg (To) to pick up the call, the audio file specified in this parameter will be played. Please refer this article to understand the format specifications of the audio file to be played (wav is only supported). Guidelines on usage:
Example: "http://{your_hosted_endpoint}/{audio file name}.wav" |
max_time_limit |
Optional |
Integer; The time limit (in seconds) that you want this call to last. The call will be cut after this time |
attempt_time_out |
Optional |
Integer; The time (in seconds) to ring the called parties (both first and second call leg) |
custom_field |
Optional |
String; Any application specific value like order id that will be passed back as a parameter in StatusCallback |
status_callback |
Optional |
Array; StatusCallback events to be received against the call. Answered/Terminal or both events can be subscribed.
Request example-
[
{
"event": "answered",
"url": "<webhook-url>"
},
{
"event": "terminal",
"url": "<webhook-url>"
}
]
|
Refer to the StatusCallback different event payloads that'll be received by your endpoint
{ "event_details": { "event_type": "answered" }, "call_details": { "call_sid": "906960925ec65c4bf46b90101b98156u", "direction": "outbound", "virtual_number": "+91804568XXXX", "call_state": "active", "call_status": null, "assigned_agent_details": { "user_id": "12898c52573645739e64864ad308f0a9", "user_name": null, "contact_uri": "+9199531XXXXX", "group_id": null, "group_name": null, "status": "in-progress" }, "customer_details": { "contact_name": null, "contact_uri": "+9199531XXXXX", "status": null }, "created_time": "2021-06-30T02:43:13+05:30", "updated_time": "2021-06-30T02:43:19+05:30", "start_time": "2021-06-30T02:43:19+05:30", "end_time": null, "total_duration": null, "total_talk_time": null, "custom_field": "12130y124b2f142", "app_id": null, "app_name": null, "digits": null, "recordings": null } }
{ "event_details": { "event_type": "answered" }, "call_details": { "call_sid": "906960925ec65c4bf46b90101b98156u", "direction": "outbound", "virtual_number": "+91804568XXXX", "call_state": "active", "call_status": null, "assigned_agent_details": { "user_id": "12898c52573645739e64864ad308f0a9", "user_name": null, "contact_uri": "+9199531XXXXX", "group_id": null, "group_name": null, "status": "in-progress" }, "customer_details": { "contact_name": null, "contact_uri": "+919953XXXXX", "status": "in-progress" }, "created_time": "2021-06-30T02:43:13+05:30", "updated_time": "2021-06-30T02:43:24+05:30", "start_time": "2021-06-30T02:43:19+05:30", "end_time": null, "total_duration": null, "total_talk_time": null, "custom_field": "12130y124b2f142", "app_id": null, "app_name": null, "digits": null, "recordings": null } }
{ "event_details": { "event_type": "terminal" }, "call_details": { "call_sid": "906960925ec65c4bf46b90101XXXXXX", "direction": "outbound", "virtual_number": "+91804568XXXXX", "call_state": "terminal", "call_status": "completed", "assigned_agent_details": { "user_id": "12898c52573645739e64864ad308f0a9", "user_name": "Sarthak XXXX", "contact_uri": "+9199531XXXX", "group_id": null, "group_name": null, "status": "completed" }, "customer_details": { "contact_name": "Sarthak XXXX", "contact_uri": "+919953XXXXX", "status": "completed" }, "created_time": "2021-06-30T02:43:13+05:30", "updated_time": "2021-06-30T02:43:32+05:30", "start_time": "2021-06-30T02:43:19+05:30", "end_time": "2021-06-30T02:43:32+05:30", "total_duration": null, "total_talk_time": 5, "custom_field": "12130y124b2f142", "app_id": null, "app_name": null, "digits": null, "recordings": [ { "url": "https://s3-ap-southeast-1.amazonaws.com/exotelrecordings/Exotel/906960925ec65c4bf46b901XXXXX.mp3" } ] } }
curl -X POST https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls -H 'content-type: application/json' -d '{ "from": { "user_contact_uri": "+91995312XXXX" }, "to": { "customer_contact_uri": "+91995312XXXX" }, "virtual_number": "+91804568XXXX", "recording": true, "custom_field": "12130y124b2f142", "status_callback": [ { "event": "answered", "url": "https://your-server.com" }, { "event": "terminal", "url": "https://your-server.com" } ] }'
var request = require("request"); var options = { method: 'POST', url: 'https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls', headers: { 'content-type': 'application/json', body: { from: { user_contact_uri: '0995312XXXX' }, to: { customer_contact_uri: '0995312XXXX' }, virtual_number: '+91804568XXXX', recording: true, custom_field: '12130y124b2f142', status_callback: [ { event: 'answered', url: 'https://your-server.com' }, { event: 'terminal', url: 'https://your-server.com' } ] }, json: true }; request(options, function (error, response, body) { if (error) throw new Error(error); console.log(body); });
<?php $request = new HttpRequest(); $request->setUrl('https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls'); $request->setMethod(HTTP_METH_POST); $request->setHeaders(array( 'content-type' => 'application/json', )); $request->setBody('{ "from": { "user_contact_uri": "0995312XXXX" }, "to": { "customer_contact_uri": "0995312XXXX" }, "virtual_number": "+91804568XXXX", "recording": true, "custom_field": "12130y124b2f142", "status_callback": [ { "event": "answered", "url": "https://your-server.com" }, { "event": "terminal", "url": "https://your-server.com" } ] }'); try { $response = $request->send(); echo $response->getBody(); } catch (HttpException $ex) { echo $ex; }
import requests url = "https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls" payload = '{ "from": { "user_contact_uri": "0995312XXXX" }, "to": { "customer_contact_uri": "0995312XXXX" }, "virtual_number": "+91804568XXXX", "recording": true, "custom_field": "12130y124b2f142", "status_callback": [ { "event": "answered", "url": "https://your-server.com" }, { "event": "terminal", "url": "https://your-server.com" } ] }' headers = { 'content-type': "application/json" } response = requests.request("POST", url, data=payload, headers=headers) print(response.text)
package main import ( "fmt" "strings" "net/http" "io/ioutil" ) func main() { url := "https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls" payload := '{ "from": { "user_contact_uri": "0995312XXXX" }, "to": { "customer_contact_uri": "0995312XXXX" }, "virtual_number": "+91804568XXXX", "recording": true, "custom_field": "12130y124b2f142", "status_callback": [ { "event": "answered", "url": "https://your-server.com" }, { "event": "terminal", "url": "https://your-server.com" } ] }' req, _ := http.NewRequest("POST", url, payload) req.Header.Add("content-type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) }
HTTP Response:
{ "request_id": "9e5761038e104704b684d6791178481c", "method": "POST", "http_code": 200, "metadata": null, "response": { "code": 200, "error_data": null, "status": "success", "data": { "call_sid": "9133c5d2ad930f4c7ca43645e535157d", "direction": "outbound", "virtual_number": "+91804568XXXX", "call_state": "active", "call_status": null, "assigned_agent_details": { "user_id": "12898c52573645739e64864ad308f0a9", "user_name": null, "contact_uri": "+91995312XXXX", "group_id": null, "group_name": null, "status": null }, "customer_details": { "contact_name": null, "contact_uri": "+9199531XXXX", "status": null }, "created_time": "2021-07-13T17:56:47+05:30", "updated_time": "2021-07-13T17:56:48+05:30", "start_time": "2021-07-13T17:56:48+05:30", "end_time": null, "total_duration": null, "total_talk_time": null, "custom_field": "12130y124b2f142", "app_id": null, "app_name": null, "digits": null, "recordings": null } } }
Parameter Name | Type & Value |
request_id | This indicates the unique id of the request. Useful for debugging and tracing purposes. |
method | This indicates the HTTP method for the request such as GET/POST/PUT etc. |
http_code | This indicates the HTTP code for the request such as 200, 400, 500 etc. |
response |
Response block contains the user device record matching the request URI. The block contains
|
Description of parameters under 'data' block:
Parameter Name | Description |
call_sid |
string; an alpha-numeric unique identifier of the call |
direction |
|
virtual_number |
The virtual number (ExoPhone) where the call landed (Incoming) or was used to initiate the call (Outgoing) |
call_state |
|
call_status |
|
assigned_agent_details |
assigned_agent_details will be the details of the agent who is the primary agent assigned for the call. In case of-
Fields
|
customer_details |
This is the number the caller called from (for inbound calls), or the number the call was placed to (for outbound calls), and is equal to
Fields-
|
created_time |
DateTime as per ISO 8601 date format at which the user initiated the call via API. |
updated_time | DateTime as per ISO 8601 date format at which the call data was last updated in our system Eg- 2021-07-28T02:59:34+05:30 |
start_time | DateTime as per ISO 8601 date format when the call request was initiated to the operator Eg- 2021-07-28T02:59:34+05:30 |
end_time | DateTime as per ISO 8601 date format when the call ended Eg- 2021-07-28T02:59:34+05:30 |
total_duration | Total call duration in seconds (From start_time to end_time) |
total_talk_time |
Total time a customer was on call. If there were more than one conversation, it would be the sum of all conversations. |
custom_field | The value that was passed in the custom_field parameter of the API (if set during the request) will be populated here. |
app_id |
Flow ID which was used at the start of the call. Null if flow was not used. |
app_name | Flow Name which was used at the start of the call. Null if flow was not used. |
digits |
DTMF digits pressed during the call separated by “-” for different applets. Similar to how it is represented in Call Reports. |
recordings |
Array; It will contain all recordings associated with the call. Each array will contain
|
*Some of the above parameters can be null for POST response as they'll be not set immediately after call creation
Possible error scenarios in case of this POST API -
HttpCode | Code | Description |
401 | 1010 |
Authentication failed
|
404 | 10731 |
User not found
|
409 | 1012 |
User device is unavailable
|
409 | 10705 |
User device is unverified
|
409 | 10706 |
User device is currently busy
|
404 | 10806 |
No device found for user
|
404 | 1017 |
AccountSid not found
|
404 | 10702 |
User Device not found
|
409 | 10705 |
User device is unverified
|
404 | 1021 |
Call not found / unsupported in this API
|
404 | 1022 |
Call not found / unsupported in this API
|
400 | 1001 |
account_sid is mandatory
|
400 | 1002 |
Valid values for RecordingChannels are single, dual
|
500 | 1100 |
Internal Server Error
|
500 | 1101 |
Internal Server Error
|
500 | 1200 |
Internal Server Error
|
404 | 10703 |
Device is currently unavailable (turned off)
|
500 | 1203 |
Internal Server Error
|
500 | 1300 |
Internal Server Error
|
405 | 10712 |
VOIP calls are not allowed
|
500 | 1500 |
Internal Server Error
|
500 | 1600 |
Internal Server Error
|
500 | 1601 |
Internal Server Error
|
500 | 1800 |
Internal Server Error
|
500 | 3040 |
Internal Server Error
|
500 | 3042 |
Internal Server Error
|
404 | 3043 |
Call not found / unsupported in this API
|
404 | 3044 |
Call not found / unsupported in this API
|
404 | 3045 |
Call not found / unsupported in this API
|
500 | 3046 |
Internal Server Error
|
500 | 3075 |
Internal Server Error
|
500 | 3080 |
Internal Server Error
|
500 | 4060 |
Internal Server Error
|
404 | 10716 |
Given virtual_number not found
|
404 | 10706 |
User device is currently busy
|
*These will be populated under the `error_data` block of response
To get call details of a CCM call, you will need to make an HTTP GET request to
https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid>
Example (For Singapore Stamp)-
https://<your_api_key>:<your_api_token>@ccm-api.exotel.com/v2/accounts/<your_sid>/calls/<call_sid>
Example (For Mumbai Stamp)
https://<your_api_key>:<your_api_token>@ccm-api.in.exotel.com/v2/accounts/<your_sid>/calls/<call_sid>
<your_api_key>
and <your_api_token>
with the API key and token created by you OR use Basic Authentication.<your_sid>
with your “Account sid”<subdomain>
with the region of your account<call_sid>
with the Call Sid provided during CCM Make Call API request.<your_api_key>
, <your_api_token>
and <your_sid>
are available in the API settings page of your Exotel Dashboard
curl -X GET https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid> -H 'content-type: application/json'
var request = require("request"); var options = { method: 'GET', url: 'https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid>', headers: { 'content-type': 'application/json' } }; request(options, function (error, response, body) { if (error) throw new Error(error); console.log(body); });
<?php $request = new HttpRequest(); $request->setUrl('https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid>'); $request->setMethod(HTTP_METH_GET); $request->setHeaders(array( 'content-type' => 'application/json', )); try { $response = $request->send(); echo $response->getBody(); } catch (HttpException $ex) { echo $ex; }
import requests url = "https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid>" headers = { 'content-type': "application/json" } response = requests.request("GET", url, headers=headers) print(response.text)
package main import ( "fmt" "strings" "net/http" "io/ioutil" ) func main() { url := "https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/calls/<call_sid>" req, _ := http.NewRequest("GET", url, nil) req.Header.Add("content-type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) }
HTTP Response:
{ "request_id": "57ba69db00c0432e88a5ca6d644bbd40", "method": "GET", "http_code": 200, "metadata": null, "response": { "code": 200, "error_data": null, "status": "success", "data": { "call_sid": "72827ead13a355d334338f53af71157s", "direction": "outbound", "virtual_number": "+91804568XXXX", "call_state": "terminal", "call_status": "completed", "assigned_agent_details": { "user_id": "12898c52573645739e64864ad308f0a9", "user_name": "Sarthak XXXX", "contact_uri": "+9199531XXXXX", "group_id": null, "group_name": null, "status": "completed" }, "customer_details": { "contact_name": "Sarthak XXXXX", "contact_uri": "+9199531XXXXX", "status": "completed" }, "created_time": "2021-07-28T03:07:12+05:30", "updated_time": "2021-07-28T03:07:33+05:30", "start_time": "2021-07-28T03:07:18+05:30", "end_time": "2021-07-28T03:07:32+05:30", "total_duration": null, "total_talk_time": 7, "custom_field": "12130y124b2f142", "app_id": null, "app_name": null, "digits": null, "recordings": [ { "url": "https://s3-ap-southeast-1.amazonaws.com/exotelrecordings/XXXXX/72827ead13a355d334338fXXXXXX.mp3" } ] } } }
Currently the GET API is powering data for CCM Outgoing Calls triggered via the Make Call CCM API only. Incoming / Outgoing calls connecting via flow is not yet available.
Details of GET API response-
Parameter Name | Type & Value |
request_id | This indicates the unique id of the request. Useful for debugging and tracing purposes. |
method | This indicates the HTTP method for the request such as GET/POST/PUT etc. |
http_code | This indicates the HTTP code for the request such as 200, 400, 500 etc. |
response |
Response block contains the user device record matching the request URI. The block contains
|
Description of parameters under 'data' block:
Parameter Name | Description |
call_sid |
string; an alpha-numeric unique identifier of the call |
direction |
|
virtual_number |
The virtual number (ExoPhone) where the call landed (Incoming) or was used to initiate the call (Outgoing) |
call_state |
|
call_status |
|
assigned_agent_details |
assigned_agent_details will be the details of the agent who is the primary agent assigned for the call. In case of-
Fields
|
customer_details |
This is the number the caller called from (for inbound calls), or the number the call was placed to (for outbound calls), and is equal to
Fields-
|
created_time |
DateTime as per ISO 8601 date format at which the user initiated the call via API. |
updated_time | DateTime as per ISO 8601 date format at which the call data was last updated in our system Eg- 2021-07-28T02:59:34+05:30 |
start_time | DateTime as per ISO 8601 date format when the call request was initiated to the operator Eg- 2021-07-28T02:59:34+05:30 |
end_time | DateTime as per ISO 8601 date format when the call ended Eg- 2021-07-28T02:59:34+05:30 |
total_duration | Total call duration in seconds (From start_time to end_time) |
total_talk_time |
Total time a customer was on call. If there were more than one conversation, it would be the sum of all conversations. |
custom_field | The value that was passed in the custom_field parameter of the API (if set during the request) will be populated here. |
app_id |
Flow ID which was used at the start of the call. Null if flow was not used. |
app_name | Flow Name which was used at the start of the call. Null if flow was not used. |
digits |
DTMF digits pressed during the call separated by “-” for different applets. Similar to how it is represented in Call Reports. |
recordings |
Array; It will contain all recordings associated with the call. Each array will contain
|
*Some of the above parameters would be null if not applicable for that call.
Possible error scenarios in case of this GET API -
Http Code | Error Code | Description |
404 | 3043 | Call not found / unsupported in this API |
404 | 3044 | Call not found / unsupported in this API |
404 | 3045 | Call not found / unsupported in this API |
500 | 1100 | Internal Server Error |
500 | 3042 | Internal Server Error |
500 | 3046 | Internal Server Error |
500 | 3075 | Internal Server Error |
*These will be populated under the `error_data` block of response
For handling incoming calls, you can build call flows on Exotel using various applets. The CCM programmable version of the Connect applet enables the call flow to be configured for connecting the caller to an agent along with additional parameters returned from your URL which can be configured dynamically per call. These include configurations such as record the call, maximum conversation time, etc. For detailed documentation of the same, refer here.
The dynamic URL can return agent’s phone number or UUID and state management will be performed for such calls i.e. agents will be marked busy such that no further calls can route to them. Such users need to be added on Exotel (Create users) and verified.
NOTE: This feature is currently in private beta and can be enabled on request via hello@exotel.com