0800 310 1010
← Developer docs
Method

CDR Feed

A polling feed of completed call detail records. Each call returns up to 1,000 records, plus a timer telling you how long to sleep before asking again. The server tracks where you got to, so the client doesn't have to. Free — CDR Feed calls don't count against your transaction limit.

Endpoint

GET https://api.numbergroup.com/cdrfeed

JSON 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=xxxyyy

On 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_address

Use 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_seconds field.
  • 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

ParameterRequiredDescription
api_keyYesYour API key.
max_recordsNoLower the per-response cap (cannot raise above 1,000). Useful on small clients.
last_sequenceNoForce the feed to return records strictly above this sequence. Lets you re-run a range or hold state client-side.
from_dateNoInitialise or reset the feed start to YYYY-MM-DD. Useful on first run to skip ancient history.
columnsNoComma-separated list of keys to include in each record. See the full list below.
formatNoAccepted 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 type first.
  • summary — control record. highest_session_sequence is safe to persist on your side; if you want to walk forward yourself, pass it back as last_sequence on the next call.
  • call_records — array of records. Omitted entirely when call_record_count is 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_duration
  • inbound_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_duration
  • outbound_originating_address, outbound_destination_address
  • outbound_application, outbound_privacy_indicated
  • outbound_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"])
See also

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.

Request an API key →help@numbergroup.com