CDR Feed
Endpoint
GET https://api.numbergroup.com/cdrfeedJSON only. XML is too inefficient at this volume, so passing &format=xml just returns a fail status. You don't need to send &format=json— it's the default.
Two ways to call it
Just keep pulling
https://api.numbergroup.com/cdrfeed?api_key=xxxyyyOn the very first call the server creates a control record at your account's creation date and starts from there. Every subsequent call resumes from the highest sequence already delivered to your key.
Reset or initialise to a date
https://api.numbergroup.com/cdrfeed
?api_key=xxxyyy
&from_date=2026-01-01
&columns=session_sequence,session_uuid,inbound_originating_address,outbound_destination_addressUse from_date to jump to a specific point in history (useful if you don't want to walk years of records from account creation). Use columns to narrow the response to just the fields your app needs.
The polling contract — read this before you build
- Each response has a
summary.next_permitted_fetch_secondsfield. - If it's
0, more records are available right now and you may call again immediately. - If it's a positive integer, you must sleep that many seconds before the next call. Typically 30.
- If your client hammers the feed inside the cooldown, the API will disable the CDR feed for that key. Don't do it.
That's a hard rule — there's no exponential-backoff debate to have, just obey the timer the server gives you.
Parameters
| Parameter | Required | Description |
|---|---|---|
| api_key | Yes | Your API key. |
| max_records | No | Lower the per-response cap (cannot raise above 1,000). Useful on small clients. |
| last_sequence | No | Force the feed to return records strictly above this sequence. Lets you re-run a range or hold state client-side. |
| from_date | No | Initialise or reset the feed start to YYYY-MM-DD. Useful on first run to skip ancient history. |
| columns | No | Comma-separated list of keys to include in each record. See the full list below. |
| format | No | Accepted for compatibility, but only json works. |
Response shape
{
"summary": {
"call_record_count": 3,
"next_permitted_fetch_seconds": 0,
"highest_session_sequence": 30143799
},
"call_records": [
{
"session_uuid": "5864648a-35f0-11e1-869e-e95c989c206d",
"inbound_originating_address": "441908867468",
"session_sequence": 30029881
},
...
],
"status": {"type": "success"}
}- status — always present. Check
typefirst. - summary — control record.
highest_session_sequenceis safe to persist on your side; if you want to walk forward yourself, pass it back aslast_sequenceon the next call. - call_records — array of records. Omitted entirely when
call_record_countis 0.
Uniqueness
Use session_uuid as the unique key for a call. The session_sequence looks unique but is only safe for ordering within a single feed cursor — don't rely on it across API keys.
One in, one out — and forked calls
Some calls in the NumberGroup network fork to many outbound legs (think hunt groups, ring-all). The CDR shows the inbound leg plus the outbound leg with the longest duration. So if you want to bill or report accurately on cost, use session_total_charges— it's the sum across all legs of that session, not just the two shown in this record.
Available columns
All timestamps are UTC, formatted YYYY-MM-DD hh:mm:ss.0.
Session-level
session_sequence— long; feed cursor.session_uuid— unique session identifier (the real primary key).session_started_utc— when the call arrived at the network.session_total_charges— sum of all leg charges (or credits) on this session.charging_vector— preserved P-Charging-Vector identity; NumberGroup generates one if not supplied.
Inbound leg
inbound_leg_id,inbound_initiated_utc,inbound_connected_utc,inbound_disconnected_utc,inbound_durationinbound_originating_address— the calling party. Withheld calls may be masked at the tail to meet Ofcom rules.inbound_destination_address— DDI dialled (NTS) or destination dialled (outbound trunk).inbound_application—1= Number Translation,2= Egress SIP Trunking.inbound_privacy_indicated— whether the CLI was withheld.inbound_zone_name,inbound_zone_id,inbound_timeclass,inbound_charge
Outbound leg
outbound_leg_id,outbound_initiated_utc,outbound_connected_utc,outbound_disconnected_utc,outbound_durationoutbound_originating_address,outbound_destination_addressoutbound_application,outbound_privacy_indicatedoutbound_zone_name,outbound_zone_id,outbound_timeclass,outbound_charge
Fields are only present when relevant — for example, an outbound leg that never connected won't have an outbound_connected_utc. Plan your parser accordingly.
Minimal polling loop
# Pseudocode
while True:
r = http_get("https://api.numbergroup.com/cdrfeed",
params={"api_key": KEY,
"columns": "session_uuid,session_started_utc,inbound_originating_address,inbound_destination_address,session_total_charges"})
data = r.json()
if data["status"]["type"] != "success":
log_error(data["status"])
sleep(60); continue
for cdr in data.get("call_records", []):
store(cdr) # session_uuid is your primary key
sleep(data["summary"]["next_permitted_fetch_seconds"])Need an API key?
We don't have a self-serve signup for API keys — drop us a line and we'll set you up. Tell us roughly what you're building so we can recommend the right transaction limit.