Why the meter, not the price, is the real design decision
Most teams obsess over the price point and improvise the meter. It should be the other way around. A price is a number you can change tomorrow with a pricing-page edit. A meter is a data contract: every event you ingest is shaped by it, every rollup is keyed by it, and every invoice line inherits its semantics. Change the price and last month's invoices stay valid. Change what a meter means and you have two eras of incompatible history.
In UsageBox a meter is created once with a name, a value_type, an optional unit (uom), and an agg_type, and it gets a generated ingest_key that your events reference. That is the whole surface, and each field is a decision worth five minutes of thought.
Value type: number or string
Numeric meters carry quantities: request counts, token counts, gigabyte-hours, minutes of transcription. String meters carry identities: a user id, a device id, a filename. The split matters because it constrains what aggregation can do. You can sum quantities; you cannot sum identities. UsageBox enforces this at meter creation: number meters allow sum, count, and max, while string meters allow count and unique_count.
The classic mistake is sending an identity as a number (a numeric user id summed into nonsense) or a quantity as a string (a "1024" that can only ever be counted, never added). If you catch yourself casting, stop and ask which question the invoice must answer: how much, how often, how big, or how many distinct.
The four aggregations, and the invoice each one produces
| Aggregation | Question it answers | Typical meter | Failure mode if misused |
|---|---|---|---|
sum | How much volume this month? | API calls with weight, tokens, GB transferred | Retry storms and replays inflate the bill unless ingestion is idempotent |
count | How many events this month? | Jobs run, reports generated, webhooks delivered | Counting free or internal events that should never bill |
max | What was the peak this month? | Concurrent seats, peak storage, largest cluster | One traffic spike prices the whole month; customers remember it |
unique_count | How many distinct actors this month? | Monthly active users, unique devices, distinct repos | Unstable identifiers (session ids instead of user ids) silently multiply "users" |
Defaults exist for the impatient: numeric meters default to sum, string meters default to count. Defaults are fine for a prototype. For a meter customers pay against, choose explicitly and write one sentence in your docs saying what the meter means. That sentence is the thing support will quote in a billing dispute.
Units are a promise to a human
The uom field ("calls", "tokens", "GB", "seats") never changes a computation, and that is exactly why it is easy to neglect. It is the only part of the meter a customer actually reads. A meter that bills in "requests" but ingests weighted values (a heavy endpoint counting as 5) will produce invoices where the numbers do not match the customer's own logs. Either meter raw requests and price tiers separately, or name the unit honestly ("request units"). Mismatched units are the top source of "your dashboard says 12,000 but my logs say 3,000" tickets.
Granularity: fewer meters, more dimensions
The tempting design is one meter per feature per plan per region. Resist it. Every meter is a contract you maintain forever, and a meter-per-variant explosion turns pricing changes into schema migrations. The robust pattern is a small set of meters that describe physics (requests, compute seconds, storage, distinct users) with plans and charges layered on top. If you find yourself creating "api-calls-pro-eu" you are encoding pricing into the meter, and the next pricing change will hurt. For splitting cost by customer and model, see Kata #4 on per-customer, per-model dimensions.
Decisions you cannot cheaply reverse
- Aggregation semantics. A month of sum events cannot become a max meter retroactively; the peaks were never recorded.
- Identifier choice for unique_count. Switch from user id to email mid-month and the same person counts twice.
- What counts as billable. If health checks and retries were metered from day one, excluding them later shrinks revenue and makes history incomparable. Decide the exclusion list first - our piece on bot and crawler traffic covers the non-human half of that list.
What you can change freely: price per unit, tiers, plan mapping, and the unit label. Design meters for the questions, price them for the market.
The honest take
Meter design is unglamorous and takes an afternoon, which is why it is usually skipped and then repaid as a quarter of billing archaeology. Write down, per meter: value type, aggregation, unit, identifier (for uniques), and the exclusion list. Five lines. Every billing system you might ever migrate to - including UsageBox - will reward you for having them, and idempotent ingestion handles the delivery half of correctness.