> ## 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 API: per-call and per-message billing reconciliation feed

> Export per-call and per-message records (CDRs) with their persisted price and terminal status, so finance and BI teams can reconcile delivered usage against the billed record line-by-line.

# CDR Export & Billing Reconciliation API

Every finished message (SMS, MMS, WhatsApp, email, RCS, …) and every finished call in your workspace is recorded with the exact unit it was billed on — SMS `segments`, voice `duration_seconds`, the persisted `price` + `currency` the wallet was charged at send or call-close time, and terminal timestamps. This surface returns those records as **one flat, recency-ordered feed** you filter, page, and export.

Use it to reconcile delivered usage against the billed record row-for-row. Reporting only: the feed never re-rates a record, never mutates a wallet, and never touches a provider.

**Base path:** `/usage`

**Authentication:** An organization API key (sent as `X-API-Key` or `Authorization: Bearer dv_live_sk_…`), or a session JWT for dashboard integrations. Either way, the caller must hold an owner, admin, or developer role with the `billing:read` scope — this feed is bulk billing data over the whole workspace, so both gates apply on both endpoints.

## Export a JSON page

### Page through CDRs

`GET /usage/cdr`

Returns one page of records, newest first, scoped to the calling tenant. Filtered with the same query string as the CSV export below, so the two formats always agree on which rows match.

#### Query parameters

| Field              | Type            | Notes                                                                                                                                                        |
| ------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `from`             | ISO-8601 string | Start of the window (inclusive). Defaults to 24 hours ago                                                                                                    |
| `until`            | ISO-8601 string | End of the window (exclusive). Defaults to now                                                                                                               |
| `types`            | CSV string      | Restrict to `message`, `call`, or both. Unrecognised tokens are dropped, so a stale value never errors the export                                            |
| `channels`         | CSV string      | Filter message channels (`sms`, `whatsapp`, `email`, …). Calls carry the synthetic channel `voice`, so `channels=voice` selects the calls arm                |
| `statuses`         | CSV string      | One key spans both record kinds: message statuses (`queued`/`sent`/`delivered`/`failed`/…) and call statuses (`initiated`/`answered`/`completed`/`failed`/…) |
| `direction`        | string          | `inbound` or `outbound`                                                                                                                                      |
| `include_unpriced` | string          | `true` (default) keeps records whose `price` is NULL — a row that failed to rate still shows up. Pass `false` for billed-records-only                        |
| `cursor`           | string          | Opaque continuation token from a previous response's `pagination.cursor`                                                                                     |
| `limit`            | integer         | Rows per page, 1–50,000 (default 10,000)                                                                                                                     |

#### Row fields

Every row carries the billing and ops fields needed for line-level reconciliation — endpoints, statuses, prices, never free-text message bodies:

| Field                                     | Type            | Notes                                                        |
| ----------------------------------------- | --------------- | ------------------------------------------------------------ |
| `id`                                      | string          | Message or call record id                                    |
| `type`                                    | string          | `message` or `call`                                          |
| `channel`                                 | string          | Message channel, or `voice` for calls                        |
| `direction`                               | string          | `inbound` or `outbound`                                      |
| `from_addr` / `to_addr`                   | string          | Endpoints                                                    |
| `status`                                  | string          | Terminal status                                              |
| `segments`                                | number          | Billed SMS segments (null on calls)                          |
| `duration_seconds`                        | number          | Billed call duration (null on messages)                      |
| `price`                                   | string          | The persisted charge as written at send/call-close           |
| `currency`                                | string          | ISO-4217 currency of the charge                              |
| `recorded_at`                             | ISO-8601 string | Recency key — created-at for messages, end-of-call for calls |
| `sent_at` / `delivered_at` / `failed_at`  | ISO-8601 string | Message terminal timestamps (null on calls)                  |
| `started_at` / `answered_at` / `ended_at` | ISO-8601 string | Call billability timestamps (null on messages)               |
| `sip_response_code`                       | number          | Call SIP response code, when available                       |
| `error_code` / `error_message`            | string          | Failure details for the failed arm                           |
| `contact_id`                              | string          | Attributed contact, when linked                              |
| `campaign_id`                             | string          | Attributed campaign, when linked                             |
| `href`                                    | string          | Dashboard deep link to the record                            |

<RequestExample>
  ```bash theme={null}
  curl -X GET 'https://api.orbit.devotel.io/usage/cdr?from=2026-08-01T00:00:00Z&until=2026-08-02T00:00:00Z&include_unpriced=true&limit=1000' \
    -H 'X-API-Key: dv_live_sk_your_key_here'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "range": { "from": "2026-08-01T00:00:00Z", "until": "2026-08-02T00:00:00Z" },
      "data": [
        {
          "id": "msg_01K0SXR3…",
          "type": "message",
          "channel": "sms",
          "direction": "outbound",
          "from_addr": "+15550001111",
          "to_addr": "+15559990000",
          "status": "delivered",
          "segments": 2,
          "duration_seconds": null,
          "price": "0.0123",
          "currency": "USD",
          "recorded_at": "2026-08-01T12:00:00.000Z",
          "sent_at": "2026-08-01T11:59:58.000Z",
          "delivered_at": "2026-08-01T12:00:01.000Z",
          "failed_at": null,
          "started_at": null,
          "answered_at": null,
          "ended_at": null,
          "sip_response_code": null,
          "error_code": null,
          "error_message": null,
          "contact_id": "cont_9J2…",
          "campaign_id": "camp_4RA…",
          "href": "/messages?id=msg_01K0SXR3…"
        },
        {
          "id": "call_0HX8…",
          "type": "call",
          "channel": "voice",
          "direction": "outbound",
          "from_addr": "+15550001111",
          "to_addr": "+15558880222",
          "status": "completed",
          "segments": null,
          "duration_seconds": 187,
          "price": "0.0300",
          "currency": "USD",
          "recorded_at": "2026-08-01T12:02:08.000Z",
          "sent_at": null,
          "delivered_at": null,
          "failed_at": null,
          "started_at": "2026-08-01T11:59:00.000Z",
          "answered_at": "2026-08-01T11:59:01.000Z",
          "ended_at": "2026-08-01T12:02:08.000Z",
          "sip_response_code": 200,
          "error_code": null,
          "error_message": null,
          "contact_id": "cont_9J2…",
          "campaign_id": null,
          "href": "/voice/calls/call_0HX8…"
        }
      ],
      "pagination": { "cursor": "MjAyNi0wOC0wMVQxMTowMDowMC4wMDBafGNhbGxfOWc=", "has_more": true }
    },
    "meta": { "request_id": "req_cdr_001", "timestamp": "2026-08-02T00:00:03Z" }
  }
  ```
</ResponseExample>

<Note>
  Page forward by passing the opaque `pagination.cursor` from each response as the `cursor` query parameter. A malformed cursor degrades to the first page of the window rather than an error.
</Note>

## Export a CSV file

### Download CDRs as CSV

`GET /usage/cdr/export.csv`

The same filter set as the JSON feed, returned as a downloadable CSV attachment. Rate-limited to 5 requests per minute and capped at 50,000 rows per pull; page through the JSON endpoint for larger windows.

#### Additional query parameters

| Field      | Type   | Notes                                         |
| ---------- | ------ | --------------------------------------------- |
| `filename` | string | Download filename stem, sanitized server-side |

Every export is audit-logged as `usage.cdr_exported` with the row count, window, and truncation flag.

<RequestExample>
  ```bash theme={null}
  curl -X GET 'https://api.orbit.devotel.io/usage/cdr/export.csv?types=message&channels=sms,whatsapp&filename=august-usage' \
    -H 'X-API-Key: dv_live_sk_your_key_here' \
    -O
  ```
</RequestExample>

<ResponseExample>
  ```csv 200 theme={null}
  id,type,channel,direction,from_addr,to_addr,status,segments,duration_seconds,price,currency,recorded_at,sent_at,delivered_at,failed_at,started_at,answered_at,ended_at,sip_response_code,error_code,error_message,contact_id,campaign_id,href
  msg_01K0SXR3VQ9JSTD6B6DZ0QJXM6,message,sms,outbound,+15550001111,+15559990000,delivered,2,,0.0123,USD,2026-08-01T12:00:00.000Z,2026-08-01T11:59:58.000Z,2026-08-01T12:00:01.000Z,,,,,,,,cont_9J2WYA,camp_4RAKZ,/messages?id=msg_01K0SXR3VQ9JSTD6B6DZ0QJXM6
  ```
</ResponseExample>

<Note>
  When a pull exceeds the per-page cap, the response sets `X-Export-Truncated: true` — page through with the JSON endpoint to drain the window.
</Note>
