curl --request GET \
--url https://{host}/api/v1/cdrs \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/api/v1/cdrs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/cdrs"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"items": [
{
"billing_mode": "rated",
"billsec": 123,
"call_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"direction": "inbound",
"started_at": "2023-11-07T05:31:56Z",
"transcoded": true,
"transport": "sip",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"ai_tokens": 123,
"answered_at": "2023-11-07T05:31:56Z",
"bridge_cause": "<string>",
"caller_number": "<string>",
"campaign_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"codec_a": "<string>",
"codec_b": "<string>",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"cost_amount": "0.062000",
"cost_amount_original": "0.024000",
"cost_currency": "<string>",
"cost_unconverted": true,
"currency": "<string>",
"destination_number": "<string>",
"dialled_number": "<string>",
"ended_at": "2023-11-07T05:31:56Z",
"fx_rate": "83.20000000",
"hangup_cause": "<string>",
"jitter_ms": 123,
"loss_pct": 123,
"margin": "0.062000",
"mean_interval_ms": 123,
"media_verdict": "ok",
"mos": 123,
"quality_pct": 123,
"sell_amount": "0.062000",
"sip_status": 123,
"team_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"limit": 123,
"offset": 123,
"total": 123
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}List call detail records
Newest first. A team_supervisor or agent is restricted to their own team regardless of filters. Sort keys: billsec, caller_number, destination_number, direction, ended_at, sell_amount, started_at. An unknown sort key is a 400.
curl --request GET \
--url https://{host}/api/v1/cdrs \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://{host}/api/v1/cdrs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/cdrs"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"items": [
{
"billing_mode": "rated",
"billsec": 123,
"call_uuid": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"direction": "inbound",
"started_at": "2023-11-07T05:31:56Z",
"transcoded": true,
"transport": "sip",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"ai_tokens": 123,
"answered_at": "2023-11-07T05:31:56Z",
"bridge_cause": "<string>",
"caller_number": "<string>",
"campaign_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"codec_a": "<string>",
"codec_b": "<string>",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"cost_amount": "0.062000",
"cost_amount_original": "0.024000",
"cost_currency": "<string>",
"cost_unconverted": true,
"currency": "<string>",
"destination_number": "<string>",
"dialled_number": "<string>",
"ended_at": "2023-11-07T05:31:56Z",
"fx_rate": "83.20000000",
"hangup_cause": "<string>",
"jitter_ms": 123,
"loss_pct": 123,
"margin": "0.062000",
"mean_interval_ms": 123,
"media_verdict": "ok",
"mos": 123,
"quality_pct": 123,
"sell_amount": "0.062000",
"sip_status": 123,
"team_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
],
"limit": 123,
"offset": 123,
"total": 123
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Authorizations
Every request sends Authorization: Bearer <token>. The token is either a panel session (a JWT from /auth/login, 12 hours) or an API key ft_<id>_<secret>. An API key is accepted only from an address on its IP allowlist (403 ip_not_allowed otherwise; 403 ip_allowlist_required for an old key that has none), is limited to its rate per minute (429 rate_limited with Retry-After; X-RateLimit-Limit/Remaining/Reset on every response), and at most 60 call placements a minute.
Query Parameters
Filter on the extension the call was on.
Filter on the number dialled, matched as a case-insensitive SUBSTRING: "7231" finds every call to a number ending 7231. A % or _ in the value is matched literally, not as a wildcard. For an inbound call the number dialled is the DID it arrived on; it also matches an outbound call to that number, so pair it with direction=inbound for arrivals only.
Calls placed by one campaign. The column has been on the CDR since the campaign tables were created and was neither returned nor filterable, so "what did this campaign cost" -- in minutes or in tokens -- could not be asked here.
Inclusive lower bound on started_at. RFC3339 timestamp or YYYY-MM-DD.
Exclusive upper bound on started_at. RFC3339 timestamp or YYYY-MM-DD.
Filter on team.
Filter on agent.
Filter on direction: inbound or outbound.
x <= 500Narrow to one organisation. Outside your scope returns 403.
Sort key. Each list accepts its own allow-list of keys, given in that endpoint's description; anything else is a 400. ORDER BY cannot be parameterised, so the key is looked up rather than interpolated. Every sort carries a tiebreak on the primary key, so paging stays disjoint when the sort column has duplicates.
Sort direction. Defaults to ascending, except the call log, which reads newest first.
asc, desc Free-text search across the list's searchable columns (case-insensitive substring). % and _ in the term match themselves.
Filter on transport.
Filter on hangup cause.
Only transcoded calls.
Filter on trunk.
Only calls billed at least this many seconds.