curl --request GET \
--url https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics \
--header 'Authorization: Basic <encoded-value>'import requests
url = "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Basic <encoded-value>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics")
.header("Authorization", "Basic <encoded-value>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Basic <encoded-value>'
response = http.request(request)
puts response.read_body{
"campaign_id": "42",
"contact_states": {
"pending": 120,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"contact_states_dialable": {
"pending": 118,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"contact_states_repeated": {
"pending": 31,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"pending_by_category": {
"favorite": 6,
"rescheduled": 12,
"retry": 22,
"fresh": 80
},
"total_contacts_at_range_end": 165,
"open_assignments": 3,
"assigned_by_state": {
"pending": 2,
"scheduled": 1
},
"assigned_pending_by_category": {
"favorite": 0,
"rescheduled": 0,
"retry": 1,
"fresh": 1
},
"total_dialed": 240,
"total_answered": 150,
"answer_rate": 0.625,
"num_rescheduled": 12,
"num_dropped": 3,
"num_dispositioned": 96,
"num_conversions": 30,
"conversion_rate": 0.3125,
"num_reached_human": 130,
"num_reached_voicemail": 15,
"num_reached_other_machine": 5,
"attempt_no_split": {
"first_attempt": 152,
"repeat_attempt": 88
},
"distinct_called_contacts": null,
"distinct_answered_contacts": 112,
"call_durations": {
"calls": 238,
"answered_calls": 150,
"talk_seconds": 21540,
"total_seconds": 26310,
"ring_seconds": 4770,
"avg_talk_seconds": 144,
"avg_ring_seconds": 20,
"max_talk_seconds": 1260
},
"disposition_breakdown": [
{
"tag_id": "100",
"count": 17,
"name": "Interested",
"color": "#00AA55",
"outcome": "successful_positive"
},
{
"tag_id": "101",
"count": 9,
"name": "Call back later",
"color": "#FFAA00",
"outcome": "rescheduled"
}
],
"updated_at": "2026-08-04T11:40:00.000Z",
"range": null
}{
"statusCode": 400,
"message": "Validation error",
"error": "Bad Request",
"correlationId": "YevPQs",
"subErrors": [
"incorrect email"
]
}{
"code": "UNAUTHORIZED",
"message": "You are not authorized to access this resource."
}{
"statusCode": 404,
"message": "Not Found",
"error": "Not Found",
"correlationId": "YevPQs"
}{
"statusCode": 422,
"message": "Unprocessable Entity",
"error": "Unprocessable Entity",
"correlationId": "YevPQs"
}{
"statusCode": 429,
"message": "Too Many Requests",
"error": "Too Many Requests",
"correlationId": "YevPQs"
}{
"statusCode": 500,
"message": "Internal Server Error",
"error": "Internal Server Error",
"correlationId": "YevPQs"
}Get a campaign's statistics
Returns the campaign’s aggregated performance on one of two bases, told apart by range in the response:
- All-time (no
dateFrom/dateTo): lifetime counters served from a projection refreshed in the background —updated_attells you how fresh they are, andrangeis null.call_durationsis included only whenincludeDurations=true(it is the one read whose cost grows with the campaign). - Ranged (
dateFromanddateToboth set): counters aggregated at request time over the half-open window[dateFrom, dateTo).rangeechoes the resolved window,updated_atis null, and durations are always included (includeDurationsis ignored).
Both bounds are RFC 3339 date-times. The service also reads the ISO 8601 date-only form YYYY-MM-DD, which lies outside that shape and is therefore only reachable from a client that does not validate against this schema: a date-only dateTo names a calendar day and is served inclusive of it — the exclusive end moves to the following UTC midnight, so equal date-only bounds are a valid one-day window. There is no limit on the window’s length; the only bound is the 13-month event retention horizon on dateFrom. Supplying only one bound, an empty window, or a dateFrom past the horizon is a 422.
The membership-state blocks (contact_states*, pending_by_category, open_assignments) always describe the campaign’s current state — a window does not narrow them. Unknown query parameters are rejected with 400 rather than ignored. A campaign that has never been dialed returns zeroed counters, not 404.
curl --request GET \
--url https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics \
--header 'Authorization: Basic <encoded-value>'import requests
url = "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics"
headers = {"Authorization": "Basic <encoded-value>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Basic <encoded-value>'}};
fetch('https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Basic <encoded-value>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics")
.header("Authorization", "Basic <encoded-value>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudtalk.io/v1/dialer/campaigns/{campaignId}/statistics")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Basic <encoded-value>'
response = http.request(request)
puts response.read_body{
"campaign_id": "42",
"contact_states": {
"pending": 120,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"contact_states_dialable": {
"pending": 118,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"contact_states_repeated": {
"pending": 31,
"scheduled": 4,
"completed": 38,
"dnc": 2,
"exhausted": 1,
"excluded": 0
},
"pending_by_category": {
"favorite": 6,
"rescheduled": 12,
"retry": 22,
"fresh": 80
},
"total_contacts_at_range_end": 165,
"open_assignments": 3,
"assigned_by_state": {
"pending": 2,
"scheduled": 1
},
"assigned_pending_by_category": {
"favorite": 0,
"rescheduled": 0,
"retry": 1,
"fresh": 1
},
"total_dialed": 240,
"total_answered": 150,
"answer_rate": 0.625,
"num_rescheduled": 12,
"num_dropped": 3,
"num_dispositioned": 96,
"num_conversions": 30,
"conversion_rate": 0.3125,
"num_reached_human": 130,
"num_reached_voicemail": 15,
"num_reached_other_machine": 5,
"attempt_no_split": {
"first_attempt": 152,
"repeat_attempt": 88
},
"distinct_called_contacts": null,
"distinct_answered_contacts": 112,
"call_durations": {
"calls": 238,
"answered_calls": 150,
"talk_seconds": 21540,
"total_seconds": 26310,
"ring_seconds": 4770,
"avg_talk_seconds": 144,
"avg_ring_seconds": 20,
"max_talk_seconds": 1260
},
"disposition_breakdown": [
{
"tag_id": "100",
"count": 17,
"name": "Interested",
"color": "#00AA55",
"outcome": "successful_positive"
},
{
"tag_id": "101",
"count": 9,
"name": "Call back later",
"color": "#FFAA00",
"outcome": "rescheduled"
}
],
"updated_at": "2026-08-04T11:40:00.000Z",
"range": null
}{
"statusCode": 400,
"message": "Validation error",
"error": "Bad Request",
"correlationId": "YevPQs",
"subErrors": [
"incorrect email"
]
}{
"code": "UNAUTHORIZED",
"message": "You are not authorized to access this resource."
}{
"statusCode": 404,
"message": "Not Found",
"error": "Not Found",
"correlationId": "YevPQs"
}{
"statusCode": 422,
"message": "Unprocessable Entity",
"error": "Unprocessable Entity",
"correlationId": "YevPQs"
}{
"statusCode": 429,
"message": "Too Many Requests",
"error": "Too Many Requests",
"correlationId": "YevPQs"
}{
"statusCode": 500,
"message": "Internal Server Error",
"error": "Internal Server Error",
"correlationId": "YevPQs"
}Authorizations
HTTP Basic Authentication. Use your API Access Key ID as the username and your API Access Key Secret as the password. Generate keys in the CloudTalk Dashboard under Account -> Settings -> API Keys (https://dashboard.cloudtalk.io/menu/account/settings/API-keys).
Path Parameters
Dialer campaign ID.
^[1-9][0-9]*$Query Parameters
Inclusive lower bound of the statistics window, as an RFC 3339 date-time. Requires dateTo. Must not be older than the 13-month event retention horizon.
Exclusive upper bound of the statistics window, as an RFC 3339 date-time. Requires dateFrom. A date-only value — which the date-time format does not admit, so only a client that does not validate against this schema sends one — is served inclusive of that day.
Set to true to include the call_durations block on an all-time read. Ignored on a ranged read, which always carries durations. Only the literal strings true and false are accepted — anything else is a 400.
Response
The campaign's statistics.
Aggregated campaign performance. range tells the basis apart: null means all-time (cumulative over the campaign's lifetime, updated_at set), non-null means the counters cover exactly that window (updated_at null). The membership-state blocks always describe the campaign's current state regardless of any window.
Campaign ID the statistics belong to.
"42"
Contact counts per campaign-membership state.
Show child attributes
Show child attributes
Per-state count of contacts that have at least one phone number — a breakout of contact_states, not a filter.
Show child attributes
Show child attributes
Per-state count of contacts already attempted at least once — a breakout of contact_states, not a filter. Subtract it from contact_states for the never-attempted counts.
Show child attributes
Show child attributes
Breakout of contact_states.pending by waterfall category — the four sum exactly to the pending total. Includes non-dialable pending contacts, so it is not an available-to-dial count.
Show child attributes
Show child attributes
Contacts that were members of the campaign at the window's end (on an all-time read: the full current membership).
165
Contacts currently held by agents. Always a current-state count, never narrowed by the window.
3
open_assignments split by the held contact's current state. pending is the slice to subtract from a waiting reading (contact_states_dialable.pending), scheduled the slice to subtract from a scheduled reading — a held contact keeps its state while an agent works it. A breakout of open_assignments, not a filter. Always a current-state count, never narrowed by the window.
Show child attributes
Show child attributes
Waterfall-category slices of the PENDING held contacts only — which categories the contacts agents currently hold were taken from. Scoped like pending_by_category; sums to assigned_by_state.pending. Always a current-state count, never narrowed by the window.
Show child attributes
Show child attributes
Calls placed.
240
Calls answered.
150
total_answered / total_dialed, in the range 0–1.
0.625
Calls dispositioned as rescheduled.
12
Calls dropped before an agent could take them.
3
Calls that received a disposition.
96
Calls dispositioned with a positive outcome.
30
num_conversions / num_dispositioned, in the range 0–1.
0.3125
Attempts that reached a human.
130
Attempts that reached a voicemail.
15
Attempts that reached another machine (IVR, queue, tone).
5
Attempts split by whether they were the contact's first attempt or a repeat.
Show child attributes
Show child attributes
Distinct contacts dialed inside the window. Ranged-only — null on every all-time response.
null
Distinct contacts that answered. Never null; an all-time read counts contacts whose first answer is on record, a ranged read counts distinct answered contacts inside the window.
112
Duration aggregate over the calls the basis covers; all values are whole seconds. Null means the block is not on the response — an all-time read without includeDurations=true, or a failed-open duration lookup. An all-zero block is a real answer: the window's calls simply carry no recorded duration yet (calls can trail the dialed count — duration records lag the dial). Zero and null must be treated as different states.
Show child attributes
Show child attributes
Per-tag disposition counts.
Show child attributes
Show child attributes
Freshness of the all-time projection (ISO 8601, UTC). Null on every ranged response, and null on an all-time response whose projection has not materialized yet.
"2026-08-04T11:40:00.000Z"
The resolved statistics window — the field that tells you which basis the counters are on: null on an all-time response, set on a ranged one. to is exclusive; a date-only dateTo is echoed here already advanced to the following UTC midnight, so the echo may not equal the request literally.
Show child attributes
Show child attributes