> ## 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.

# The usage metering pipeline: from a sent message to the wallet ledger

> End to end, one model: how a send resolves a sender and a destination cost per message, writes one append-only usage record, aggregates into the rollups invoices are built from, and stays replayable — plus metering vs billing, currency units, and where each step is documented.

# The usage metering pipeline

Every billable thing in Devotel Orbit — a message, a completed call, an AI
request — flows through the same pipeline: resolve the sender, resolve the
destination cost, write one usage record, debit the wallet once, and roll
the row into the aggregates your invoices and dashboards read. Read this
page when you need the whole loop in a single model — the detail pages
([sender and routing](/concepts/sender-and-routing),
[pricing and rate resolution](/concepts/pricing-rate-resolution),
[the usage-records feed](/concepts/usage-records-model),
[the channel cost rollup](/concepts/cost-channel-rollup-model), and
[the wallet ledger](/concepts/wallets-credits-and-charges)) each zoom in on
one stage of what is laid out end to end here.

## 1. The chain: sender resolution → destination cost → per-message quote

A send becomes a billable event at the moment the platform can state who
you are sending as and what the destination costs.

1. **Sender resolution.** The send path picks the sender identity it will
   bill on — which owned number, sender-ID, or channel identity carries the
   message. The resolution order is fixed, documented on
   [sender resolution](/concepts/sender-resolution) and
   [sender and routing](/concepts/sender-and-routing).
2. **Destination cost resolution.** For SMS the per-operator lookup
   (MCC/MNC) runs first — override verbatim, operator cost × your markup,
   then the platform-default markup — before falling through to the generic
   per-country rate ladder. Voice resolves a per-minute rate the same way.
   The full precedence is on
   [pricing and rate resolution](/concepts/pricing-rate-resolution).
3. **Per-message cost quote.** One shared resolution step returns the full
   price breakdown: the per-unit price, the quantity multiplier (SMS
   segments, call minutes, AI tokens), the total, and the attribution label
   naming the rule that produced the price. This quote is stamped on the
   usage record before the wallet moves. The wallet pre-flight then checks
   the balance against the quote, and only then does the send proceed.

Because every channel funnels through that one resolution step, the price
you can read on a rate card is the price the ledger charges — the
[display-matches-charge invariant](/concepts/pricing-rate-resolution)
guards exactly this equality.

## 2. The usage-record write: one row per billed thing, append-only

The rated send writes exactly one **usage record** — the row-level ledger
fact you reconcile against. One record per message, one per completed call
(see [the usage-records feed](/concepts/usage-records-model) for the full
field set):

* The record carries the billed **units** (SMS segments per message, one
  unit otherwise) and the **price** the resolution quote computed, in the
  wallet currency.
* A record that failed to rate stays on the feed with an empty `price`
  rather than disappearing, so the feed always counts the same rows the
  aggregate counters count.
* The feed is **append-only in effect**: a reversal or correction is a new
  row, never an in-place edit — the same property the wallet ledger relies
  on.

The same single-debit property holds on the money side. Each usage record
produces exactly one wallet debit whose ledger reference names the
underlying record id, and every balance mutation carries an idempotency key
derived from the per-send identifier, so a retried charge collapses to one
ledger row instead of double-billing (the key mechanism is on
[wallets, credits, and charges](/concepts/wallets-credits-and-charges)).
Out-of-wallet metering — AI agent cost attribution and reseller-facing
usage metering — reports its own stores; the wallet-drawdown chain on this
page describes the channels the prepaid wallet actually charges.

## 3. Aggregation lifetime: raw records → rollups → invoice lines

The usage record is the only source of truth. Every higher-order number is
an aggregate over it:

* **Row feeds.** `GET /api/v1/usage-records` and its CSV export are the
  raw window — one page or file of records, ungrouped.
* **Counter rollups.** The aggregate counter surface
  (`GET /api/v1/messages/usage/records`) rolls the same rows up per channel
  and direction — sms-inbound, sms-outbound, voice, and so on — over a
  configurable window, optionally split per country.
* **Channel cost rollups.** `GET /api/v1/analytics/costs` groups priced
  rows by channel and currency over a day/week/month granularity — the
  Insights → Costs dashboard reads this projection.
* **Invoice lines.** An invoice is assembled by summing the filtered rows
  for the billing period — the invoice is built from the ledger, never
  from a standing counter.

Crucially, **none of these rollups is a mutable accumulator**. Each one is
a function of the raw usage records in the window, so it can always be
re-derived: drop the window's filters onto `GET /api/v1/usage-records`,
sum `units × price`, and you rebuild any rollup the period contains. The
[rollup vs ledger reconciliation model](/concepts/cost-channel-rollup-model)
documents why the projections can legitimately disagree (filters, not
missing data) and how to join them back to the raw rows.

## 4. Metering vs billing: recording is unconditional, charging depends on the plan

Metering and billing are decoupled by design:

* **Metering is the recording of the event.** The usage record is written
  whenever the billable thing happens — before any plan evaluation and
  independently of what your subscription includes.
* **Billing is the decision to charge.** Whether the record debits the
  wallet depends on what the rating pipeline resolved: included quantity,
  graduated tiers, free-tier, or a zero-price rule.

The consequence matters for plan changes: **usage already exists before a
plan upgrade.** When you move from a free tier to a usage plan, the
records for everything you sent do not appear retroactively — they were
there all along, with the price column empty or unrated per the old plan's
rules. After the upgrade, only new events resolve a price. This is why the
feed never "catches up" after an upgrade: the records were never missing,
only unpriced.

Use this distinction in reconciliation as well: the usage-records feed
counts every billed event, including ones the plan priced at zero, while a
cost rollup that filters to `price > 0` reports only the charged subset.

## 5. Backfill and replay: re-aggregation must be idempotent

Two replay guarantees define the pipeline:

* **Ledger replay collapses.** A retried charge carrying the same
  idempotency key resolves to the same ledger row — the debit lands
  exactly once, and the replayed response is flagged so side effects
  (webhooks, notifications) do not refire.
* **Re-aggregation re-derives.** Any rollup is additive over the raw
  records, so backfilling or recomputing a window is a re-read of those
  records, not a re-charge. Rebuilding the channel rollup for last month
  cannot move money twice; it recomputes sums from the same append-only
  rows.

The practical consequence for your own pipelines: treat the usage-records
export as a re-runnable pull. Re-pull a window, dedupe on the record id,
and re-aggregate — idempotency holds at the row level, the same way the
wallet holds it at the debit level.

## 6. Currency and units: cents, smallest unit, no float

All money arithmetic is integer, always:

* The wallet stores **whole cents**; the charge ledger tracks sub-cent
  precision in **integer micro-cents** (1 USD = 100,000,000 micro-cents),
  so a million sub-cent debits sum exactly.
* Every usage record and rollup row carries an ISO-4217 `currency`, and
  multi-currency sums stay split per currency. Never merge currencies;
  the dashboard merges only for display and the API remains the source of
  truth.
* Field-scale representations like `balance_usd` are display-only floats.
  Do any comparison or projection in `balance_cents` or
  `balance_micro_cents` — a float projection will disagree with the ledger
  on small amounts.

The units ladder and the three-representation balance response are spelled
out on [wallets, credits, and charges](/concepts/wallets-credits-and-charges).

## 7. Where to go next

* [Usage records: the per-record billing feed](/concepts/usage-records-model)
  — the row-level feed, its filters, pagination, and reconciliation loop.
* [How billing meters your usage](/concepts/billing-and-wallet) — the
  wallet, rating pipeline, FX on converted top-ups, and tax.
* [Wallets, credits, and charges](/concepts/wallets-credits-and-charges) —
  the append-only ledger, units, pause gates, and idempotency.
* [Channel cost rollup vs the billing ledger](/concepts/cost-channel-rollup-model)
  — why the aggregate surfaces and the ledger legitimately disagree, and
  how to reconcile them.
* [Pricing and rate resolution](/concepts/pricing-rate-resolution) — the
  per-operator precedence behind the resolved price stamped on each record.
* [Usage Metering API reference](/api-reference/usage-metering) — the
  reseller-facing rating surface (stateless preview; does not touch the
  wallet).
* [CDR export & billing reconciliation API](/api-reference/cdr-export) —
  the endpoint contract for the usage-records feed and export.
