A customer opens their invoice and asks why it's higher than last month. Answering that question requires tracing a number on a PDF back through pricing rules, entitlement deductions, an aggregation window, and a stream of raw events your product emitted days ago. If any step in that chain is undocumented or non-deterministic, the honest answer is that you don't know.
Usage calculation is the chain that makes that question answerable. It runs from a raw event to a priced line item, and each step changes the number.
How is customer usage calculated for billing?
Usage is calculated by defining a billable unit, cleaning the raw event stream, aggregating events into a quantity per customer per period, applying plan pricing to that quantity, and writing a billable record that shows the quantity, the rate, and the resulting charge. The invoice is assembled from those records.
Seven steps, in order.
1. Define the billable unit
The billable unit is the thing you count. It decides your pricing, your customer's mental model of the product, and how much explaining an invoice needs.
Common units in AI and infrastructure products:
- API calls. One request, one unit. Easy to measure and easy to explain.
- Tokens. Works for language model products, but only if customers understand what a token is. If they don't, you inherit that explanation in every support thread.
- Compute minutes or GPU hours. Reasonable when cost tracks time on hardware.
- Unique agents or sessions. Counts distinct things active in a period rather than total volume.
Pick a unit your team can measure precisely and a customer can predict. A unit that's cheap to compute but abstract to the buyer will cost you more in support than it saves in engineering.
2. Pre-process the raw usage data
Products emit logs, traces, and request records, not billing summaries. Before anything is counted, the stream has to be shaped.
Attribute every event. Group by customer, and tag with product, feature, or SKU so pricing can be applied selectively later.
Resolve account hierarchies. If one contract covers several teams or subsidiaries, usage may need to pool to a parent for billing while still reporting separately per child.
Filter what should not be billed. Internal test traffic, sandbox environments, and failed requests usually shouldn't count. Write those rules down, because they'll be questioned during an audit.
Subtract included allowances. A plan that includes 100,000 tokens a month removes those before charging begins, and the allowance usually resets on the billing period.
Normalize units. You may meter in seconds and bill in minutes, or meter per event and bill in blocks of 10,000. Rounding direction is a pricing decision, so make it explicit rather than inheriting whatever the language does.
This step is unglamorous and it's where most silent revenue leakage starts.
3. Aggregate usage
Aggregation converts many events into one quantity. The function you choose has to match how the product delivers value.
| Strategy | Produces | Fits |
|---|---|---|
| Sum | Total of a property across events | API calls, tokens, compute seconds |
| Count | Number of events | Requests, jobs run |
| Count unique | Distinct values of a property | Active users, agents deployed |
| Latest | Most recent value in the period | Seat count, concurrent sessions |
| Max | Highest value seen | Peak concurrency |
| Sum with multiplier | Summed property scaled by a rate | Tokens weighted by model |
| Weighted sum | Time-proportional total | Capacity held over a period |
Defaulting to sum because it's easy will show up later as a pricing model that doesn't match what customers think they're buying. Flexprice exposes these as configuration on a metered feature rather than code, and the aggregation reference covers the behaviour of each one.
The event carries whatever the aggregation needs to read:
{
"event_name": "model.usage",
"external_customer_id": "cust_123",
"properties": {
"tokens": 1840,
"model": "gpt-4"
},
"event_id": "evt_9f2c1a",
"timestamp": "2025-08-22T07:05:49.441Z"
}
event_id is optional in the payload and worth setting anyway. It's what lets a retried request resolve to one billable event instead of two.
4. Apply pricing logic
Pricing is not quantity times rate. It's quantity evaluated against the rules attached to that customer's plan.
Plan rules first. Check what plan the customer is on and what it includes before pricing anything.
Tiers. Say the rate is $0.002 for the first million units and $0.001 after that. There are two legitimate ways to read those tiers, and they produce different invoices.
Under slab (also called graduated) pricing, usage is split at the boundary and each slice is priced at its own rate. Under volume pricing, the total lands in one tier and the whole quantity is priced at that tier's rate. For 1.5M units, slab gives $2,500 and volume gives $1,500. Neither is wrong, but picking one by accident is, and this is where hand-rolled billing code tends to disagree with the pricing page.
Flexprice makes this an explicit flag rather than an assumption. A price with billing_model: TIERED carries a tier_mode of either SLAB or VOLUME. The volume tiered doc walks through the volume case, where 65,000 calls priced against a tier ending at 100,000 at $0.0006 bills as 65,000 x $0.0006 = $39 rather than accumulating the cheaper earlier tiers.
Slab is the mode the function below implements:
def price_tiered(quantity, tiers):
"""Slab/graduated: each slice priced at its own rate.
tiers: list of (up_to, rate); up_to=None means unbounded."""
total = 0.0
remaining = quantity
previous = 0
for up_to, rate in tiers:
span = remaining if up_to is None else min(remaining, up_to - previous)
if span <= 0:
break
total += span * rate
remaining -= span
previous = up_to if up_to is not None else previous
return total
price_tiered(1_500_000, [(1_000_000, 0.002), (None, 0.001)])
# 2500.0
Whichever mode you pick, state it on the pricing page in the customer's terms. "You pay $0.001 on everything once you pass a million" and "the first million stays at $0.002" describe different bills.
Minimums and caps. Some contracts carry a monthly minimum or a spend ceiling. Both are applied after usage is priced, and both change the total.
Time splits. If the plan changed mid-period, the period splits and each segment prices at the rate in effect then. Upgrades, downgrades, and price changes all land here.
Currency and tax. Both belong inside the pricing step. Treating them as post-processing on a finished total tends to produce rounding that doesn't reconcile.
5. Generate billable records
A billable record is the row that explains a line on the invoice. It's what you show a customer who disputes a charge.
Each record should carry the customer identifier, the product or feature, the billing period, the billable quantity after entitlements, the rate or rates applied, and the final charge.
{
"customer_id": "cust_123",
"feature": "model.usage",
"period": { "start": "2025-08-01", "end": "2025-08-31" },
"billable_quantity": 1500000,
"unit": "tokens",
"tier_mode": "SLAB",
"rates": [
{ "up_to": 1000000, "rate": 0.002 },
{ "up_to": null, "rate": 0.001 }
],
"amount": 2500.00
}
If the record can't reproduce the charge on its own, invoicing, collections, and revenue recognition all become brittle downstream.
6. Store and sync the records
Billable records are ledger data, not scratch output.
Keep them in a durable, auditable store that acts as the single source of truth for usage charges. Invoicing reads from it rather than recomputing. Finance systems sync from it so month-end reconciliation is a comparison rather than an investigation. Customer dashboards read from it so a user can trace a charge to their own activity without opening a ticket. Every adjustment, whether a credit, a correction, or a late-arriving event, gets logged so the number is explainable a year later.
Flexprice documents the order in which credits, discounts, and taxes are applied in Invoice Calculation Order, which is the part most often reverse-engineered from an invoice.
7. Backtest and iterate
A billing cycle closing is the start of the feedback loop, not the end of it.
Re-run past usage against a proposed pricing change before shipping it. You'll see where revenue would have moved and which customers would have been surprised. Investigate invoices that spiked, because those usually expose an aggregation edge case rather than a customer behaviour change. Look for events you're emitting but not billing, which is revenue leakage that no alert will tell you about. Then feed usage patterns back to the people making pricing decisions, since high usage and high revenue are frequently not the same customers.
Where Flexprice fits
Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud.
It implements the chain above as configuration: a metered feature defines the event name and aggregation, prices attach to that feature on a plan, wallets and credit grants draw down against the result, and invoices assemble from the billable records. Because it's open source and self-hostable, usage and revenue data can stay entirely inside your own infrastructure and never reach a vendor's cloud.
Run it locally and push a few events through the chain before deciding whether to build it yourself: github.com/flexprice/flexprice.
Top comments (0)