Operator-facing surface. This guide walks the Billing → Records
dashboard page (
/billing/records). For the API the page reads from —
GET /usage/cdr and GET /usage/cdr/export.csv — see
CDR usage export for billing reconciliation.
The dashboard and the API expose the same usage records; the page is the
faster path for an operator who wants to filter, eyeball, and download CSV
without leaving the console.Billing → Records: the usage record export console
Open Billing → Records (/billing/records) and you land on the single
page built for one job: turn a month of calls and messages into two CSV files
finance can reconcile line-for-line against the wallet ledger. The page pairs
a live usage feed with two export panels — one per-call (voice CDR), one
per-message — over one shared filter window, so the records you see in the
table are the records you download.
This is the dashboard walkthrough. For the request/Response contract, the
keyset-cursor drain loop, the CSV column order, and the per-bucket
reconciliation script, read the
API-focused guide. This
guide assumes you are an operator in the console, not at a terminal.
1. What the page is
/billing/records is the shared-window CSV export hub. It unifies the two
billing-reconciliation surfaces — voice CDR (one row per call) and
messaging usage (one row per message) — that previously lived as separate
exports, and sits them over one filter strip so both exports honor the same
window, the same channel scope, and the same status slice. Set the filters
once; both panels and the live table read the same coverage.
The page exists so a finance operator can answer “what did we spend this
month, broken down to the record” in one click rather than one script. The
role gate matches the API: owner, admin, or billing. If you can open the
page you can export its data; if you cannot, the API is equally gated.

2. The shared-window filter strip
Every control in the filter card at the top of the page applies to the live table and both export panels at once. Change one filter and all three surfaces re-scope to the same window — there is no per-panel filter drift. The controls, left to right:- Window (7d / 30d / 90d) — a preset “last N days” range picker in the
page header. The default is 30d. The window resolves to an inclusive
fromand an exclusiveto(now) in UTC, so the live table and the CSV export see the same rows for the same window. - Direction —
All(both legs),Inbound, orOutbound. Applies to both call and message records. - Channel —
All channels, or one ofSMS,MMS,WhatsApp,Voice,Email,RCS. Calls carry the syntheticVoicechannel, so selecting it scopes the table to the call arm. - Record kinds — toggle chips for
CallsandMessages. Leave both off for both record kinds; toggle one to drain a single kind (the server-side resolver drops the disabled arm rather than over-fetching). - Status — a free-form text field with suggestions (
delivered,sent,queued,failed,undelivered,bounced,expired,answered,completed). One key spans both record kinds; an unrecognized token matches nothing but never errors the request. - Phone (CDR export only) — an E.164 phone number. This filter reaches only the voice CDR export card — the unified message-export schema has no phone bound, so the field would silently no-op there. The page labels it accordingly so a scoped export never silently drops to “all numbers.”
- Pricing —
All records (priced + unpriced)orPriced only. The default keeps rows whose price was never rated so the feed never looks like “no usage” when a rating hiccup left a NULL price. Switch toPriced onlyto restrict to billed rows.
3. Voice panel — call records (voice CDR)
The Call records (voice CDR) export card streams one row per call, priced with the settled per-minute rate the wallet was charged at call close. It drives the voice export endpoint with the shared window and the direction, status, and phone filters from the strip above. Pick the direction at the strip —All, Inbound, or Outbound — and the
card exports that leg. Each row in the exported CSV carries:
- Direction — inbound or outbound.
- From — the calling party address.
- To — the called party address.
- Duration — the billed call duration.
- Price — the per-call settled price the wallet was charged.
- Currency — the currency the price is denominated in.
- Country — the destination country the rate card billed the call against.
usage-cdr-records-<timestamp>.csv. Up to 100,000 rows per pull; the card
priced each row at the per-minute rate the wallet ledger saw at close — the
export never re-rates.
4. Messaging panel — message records
The Message records (SMS / WhatsApp / …) export card streams one row per message across every channel in the account — SMS, MMS, WhatsApp, Email, RCS, and the rest — priced with the per-message rate the wallet was charged at send time. It drives the message export endpoint with the shared window and the channel and status filters from the strip. Each row in the exported CSV carries:- Direction — inbound or outbound.
- From / To — the sender and recipient addresses.
- Channel — the channel the message rode on (SMS, WhatsApp, Email, RCS, …).
- Segments — the billable segment count the message was rated on (the unit the carrier bills — one long SMS may be two or three segments).
- Price — the per-message settled price the wallet was charged.
- Currency — the currency the price is denominated in.
- Status — the delivery status at export time.
usage-messages-records-<timestamp>.csv. Up to 10,000 rows per pull;
channel-scope the export by setting a channel in the filter strip above
before you export — the message-export schema has no phone bound, so the
phone filter does not reach this card.
5. How the page reconciles
The live table above both export cards subscribes to the same usage feed the CDR API exposes — the records you see in the table are the records that land in the CSV. Each row carries its resolved price inline, so per-record reconciliation against the wallet ledger is exact, not an estimate. The table keyset-paginates (a server-validated(timestamp, id) cursor) with
a Load more records affordance; a Load more click fetches the next
page over the same window without resetting the filter. A Export CSV link
in the table header drives the unified usage-CDR export — the same filter set
in one pass — so the table and the export never disagree on which rows match.
This is where the dashboard and the API divide: the dashboard is
operator-friendly — filter, eyeball, download, repeat. For a
finance-grade close that pages the whole month, drains a 50,000-row cap, and
matches rows against an invoice line-for-line, the
CDR export API is the path.
The dashboard readings aggregate and round by design; the CSV the page
downloads carries the same per-record prices the API returns.
6. Role gate
The page is gated to owner, admin, or billing — the same roles the CDR export API requires. The gate matches every sibling billing surface so a finance operator never hits a 403 halfway through a reconciliation sweep. If you can open/billing/records you can read and export the feed; the API is
equally gated.
7. Cross-links
- CDR usage export for billing reconciliation
— the API-focused guide for
GET /usage/cdrandGET /usage/cdr/export.csv: keyset-cursor pagination, the CSV column order, the rate limit and row cap, and the worked month-end reconciliation script. Read it when the dashboard’s 100,000-row voice cap or 10,000-row message cap is too small, or when a warehouse sync needs the JSON drain. - Revenue recognition: read deferred vs recognized schedules — the downstream cost-center walkthrough for turning the records this page exports into a recognized-vs-deferred schedule per contract.
- Read the cost-intelligence dashboards — the Billable usage records panel on the cost-intelligence surface aggregates the same per-record feed this console exports into carrier-parity counters by SMS, MMS, and voice. Read it when the reconciliation question is “what does a carrier bill us for” rather than “which records sum to this invoice line.”