Core Concepts
Understanding these three concepts will help you work effectively with the billing platform.
1. NanoMoney — Why Not Floats?
All monetary amounts in this system are stored as i128 integers with scale=12 (pico-units).
This means 1 USD = 1_000_000_000_000 (one trillion raw units). The type is called NanoMoney internally, and field names always end with _nanos (e.g., total_nanos, amount_nanos, threshold_nanos).
Why not floats? IEEE 754 double-precision floats have about 15-16 significant decimal digits — which sounds like a lot until you’re summing millions of small charges. 0.1 + 0.2 != 0.3 in floating point. For a billing system handling millions of dollars, these errors compound into real money. Using i128 integers eliminates this class of bug entirely.
Why scale=12 (not 2)? Standard “cents” arithmetic (i64 with scale=2) breaks down for usage-based pricing. An AI API might charge $0.000001 per token — that’s 0.000001 * 10^12 = 1_000_000 pico-units. With scale=2, this would round to zero. With scale=12, we have sub-nanodollar precision.
In API responses, _nanos fields are serialized as strings to avoid JavaScript’s 53-bit integer limit. To display to users, divide by 10^12:
const dollars = BigInt(total_nanos) / BigInt(1_000_000_000_000);
const cents = (BigInt(total_nanos) % BigInt(1_000_000_000_000)) / BigInt(10_000_000_000);
console.log(`$${dollars}.${String(cents).padStart(2, '0')}`);
Rounding happens exactly once — at invoice output, when converting to Money (2-decimal display). Never round intermediate calculations.
2. Subscription Lifecycle
A subscription moves through these states:
┌─────────────┐
│ Trialing │ ← trial_days > 0
└──────┬──────┘
│ trial ends / payment succeeds
┌──────▼──────┐
┌────►│ Active │◄────┐
│ └──────┬──────┘ │
│ │ payment fails
│ ┌──────▼──────┐ │
│ │ PastDue │ │ resume()
│ └──────┬──────┘ │
│ │ dunning exhausted
│ ┌──────▼──────┐ │
│ │ Paused ├─────┘
│ └──────┬──────┘
│ │ period ends
│ ┌──────▼──────┐
└─────┤ Cancelled │
└─────────────┘
│ period ends
┌──────▼──────┐
│ Expired │
└─────────────┘
Key fields:
period_start/period_end— the current billing periodcharged_through_date— how far the subscription has been billed (advances with each successful charge)status— the current lifecycle state
State transitions are enforced — the service will reject invalid transitions (e.g., you cannot resume a Cancelled subscription).
3. The Metering → Rating → Invoicing Pipeline
The billing loop flows in three stages:
Usage Events → [Metering] → UsageQuantity (Decimal)
│
PriceSheet ───────────────→ [Rating] → RatedLineItem (i128 nanos)
│
[Invoicing] → Invoice (Money, ISO precision)
Metering receives raw usage events (CloudEvents-compatible JSON), deduplicates by event id, and aggregates quantities by meter definition. Output is always a Decimal quantity — never money. Metering doesn’t know prices.
Rating takes UsageQuantity + a PriceSheet (the plan’s pricing configuration) and produces RatedLineItem records with amount_nanos: i128. The rate_all() function is a pure function — no I/O, fully deterministic. Supports flat, per-unit, graduated, volume, package, and committed pricing models.
Invoicing collects RatedLineItem records, adds proration credits if applicable, appends tax line items if a TaxAdapter is configured, and produces a finalized Invoice. Rounding from pico-units to display currency happens exactly once at this stage.
Draft invoices can be modified (add line items, apply credits). Once finalized (POST /admin/v1/invoices/:id/finalize), the invoice is immutable and receives a sequential number (INV-XXXXXX).