> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CDR export & billing reconciliation feed

> Export per-call detail records (CSV/JSON) and pull per-category usage counters (per-channel, per-country, per-segment) — the per-call and per-message records your finance stack reconciles invoices against.

# CDR export & billing reconciliation feed

Finance reconciliation on a usage-based plan comes down to two data feeds:
the **per-call records** behind your voice charges (call detail records, CDR)
and the **per-message counters** behind your messaging charges (billable
usage records). Devotel Orbit ships both as API-level surfaces, so your
finance stack — not a shared spreadsheet — reconciles invoices.

This page covers the three pieces that make up the feed:

1. `GET /messages/usage/records` — per-category usage counters (Twilio
   `/v1/Usage/Records` parity), optionally split per country.
2. `GET /voice/calls/export` — streamed per-call detail records, CSV or JSON.
3. The Insights › Costs panel that renders the same counters in the dashboard.

***

## 1. Usage records — `GET /messages/usage/records`

Returns one counter row per billing category over a rolling window: SMS
inbound/outbound, MMS inbound/outbound, and voice inbound/outbound. Each row
carries its category, channel, direction, count, summed price, and currency.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/messages/usage/records?days=30" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

| Query parameter  | Notes                                                                                                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `days`           | Window length, 1–365 days (default 30). Off-spec or out-of-range values fall back to the 30-day default rather than an error.                                     |
| `country_splits` | Pass `1`, `true`, or `yes` to add a per-country breakdown to each record, derived from the recipient's numbering-plan prefix. Any other value disables the split. |

Example record when `country_splits=1`:

```json theme={null}
{
  "category": "sms-outbound",
  "channel": "sms",
  "direction": "outbound",
  "count": 128029,
  "price": 1040.37,
  "currency": "usd",
  "countries": [
    { "country": "US", "count": 128000, "price": 1040.00 },
    { "country": "FR", "count": 29, "price": 0.37 }
  ]
}
```

Three counting rules to know before you reconcile against your invoice:

* **SMS counts by segment, MMS and voice by message or call.** A multipart
  SMS bills per segment, so the usage counter matches what the invoice
  charges.
* **Inbound traffic is included.** Inbound SMS/MMS and inbound voice are
  billable categories and appear alongside outbound.
* **Unresolvable recipients land in an `UNKNOWN` country bucket** in the
  split view rather than being dropped — your split totals always reconcile
  to the category total.

Currency is preserved per bucket: an account that bill in more than one
currency gets one record per (category, currency) pair, so mixed windows
never sum apples with oranges.

## 2. Call detail records — `GET /voice/calls/export`

Exports per-call detail records as a streamed file — CSV by default, JSON on
request — filtered by the same dimensions as the calls list.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/voice/calls/export?format=csv&from=2026-08-01&to=2026-08-31" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -o august-call-detail-records.csv
```

| Query parameter | Notes                                                     |
| --------------- | --------------------------------------------------------- |
| `format`        | `csv` (default) or `json`.                                |
| `from` / `to`   | ISO-8601 timestamps bounding the call window.             |
| `direction`     | `inbound` or `outbound`.                                  |
| `status`        | Call status filter (for example `completed`, `failed`).   |
| `phone_number`  | Restrict the export to calls involving a specific number. |

Each exported row carries the call's id, direction, from/to numbers, final
status, duration, the rated price and currency for that call, SIP response
code, timestamps, and a downloadable recording URL when a recording exists.

The export is capped at 100,000 rows per request. Wide windows should be
pulled month-by-month. When a request would exceed the cap it is rejected
outright with a `422` error and code `EXPORT_TOO_LARGE`, which includes
`details.max_rows` (100000) — narrow the date range or filters and retry;
the failure happens before any bytes stream, so a partial file is never
written. Recording URLs are pre-signed download links — the raw storage
path never leaves the API.

## 3. Reconciling the Costs panel against the Billed messages tile

The **Insights › Costs** page renders the same usage-records feed next to a
"Billed messages" summary tile. They intentionally count different things,
and the panel explains this inline:

* **Billable usage records** counts SMS by segment and includes inbound
  traffic.
* **Billed messages** counts outbound messages that incurred a charge.

So the usage-records total being higher than the tile total is the expected
relationship — it is not a discrepancy. When your finance system reconciles,
use the usage-records counters (segment-level, inbound-inclusive), because
that matches the invoice's own counting.

## 4. Warehouse sync, if you need the rows in your own dataset

If your finance stack runs in a warehouse, the reverse-ETL mechanism mirrors
rated usage records and voice call detail records to BigQuery on a schedule —
the same per-call and per-message contract, landed in tables you own. See the
reverse-ETL configuration guides for the `usage_events` and `call_logs`
tables.
