Per-Customer, Per-Model AI Cost Without Arbitrary Dimensions

Use the actual UsageBox routing axes—subscription, product item and meter—to preserve the customer/model cuts you will need later. A hands-on pattern for per-model rollups, per-customer cost and margin without claiming free-form event dimensions.

8 min read

usagebox katadimensionscost allocationper-customer costper-model costunit economicsmetering APIanalytics2026

This is a hands-on kata, not a think-piece. The goal: turn your usage meter from a number that feeds an invoice into a management instrument you can interrogate - cost sliced by customer, by model, by feature - using nothing but the real metering API. By the end you will be able to answer "which feature is burning the most Opus tokens?" and "what is my margin on this account?" without a data warehouse, a nightly ETL, or a new table of your own.

The one idea that makes all of this work: you can only group later on what you separated now. If you want per-feature cost in three weeks, the separation has to exist in the records you send today. So this kata starts at ingest, where the leverage is. If you have already worked through Kata #1 and have idempotent ingest running, you are ready.

Step 1: your dimensions are three, and you pick them at ingest

A UsageBox record has exactly five fields: subscription, product_item, meter, value and timestamp. There is no free-form label bag. That is a constraint worth understanding rather than fighting, because it means three of those fields are your entire analytical schema:

  • subscription - who. The customer or contract being billed.
  • product_item - what they bought. The plan line the usage belongs to.
  • meter - what you are counting. This is the flexible one, and it is where model, feature and token type all end up living.

Rollups are aggregated per calendar month across those keys, so every question you will ever ask is a filter over that cube. Design it before you ingest: a record that goes to a meter called tokens can never be split into Opus and Sonnet afterwards, no matter how clever the later query is.

Step 2: name meters for the cut you will want

The practical move is a naming convention that encodes the axis you care about. If you want per-model cost, the model goes in the meter name:

curl -X POST https://api.usagebox.com/api/v1/usage \
  -H "x-api-key: $USAGEBOX_KEY" \
  -H "Idempotency-Key: collector-2026-06-16T10:14Z-northwind" \
  -H "Content-Type: application/json" \
  -d '[
    { "subscription": "northwind", "product_item": "assistant-seats",
      "meter": "opus-tokens-doc-summary",   "value": 18450,
      "timestamp": "2026-06-16T10:14:05Z" },
    { "subscription": "northwind", "product_item": "assistant-seats",
      "meter": "sonnet-tokens-chat",        "value": 9120,
      "timestamp": "2026-06-16T10:14:06Z" }
  ]'
{ "message": "Usage batch accepted", "records": 2, "processed": 2, "skipped": 0 }

Two records, one request, up to 1,000 per batch. Check processed, not the status code: a well-formed batch carrying a meter key that does not exist on your account returns 200 and meters nothing.

Pick the granularity deliberately. opus-tokens-doc-summary gives you model and feature in one axis, which is powerful but multiplies the number of meters you maintain (models times features). opus-tokens plus a separate feature-level product_item keeps the meter list small at the cost of one less cut. Both are defensible; what is not defensible is a single tokens meter, which throws away every question you have not asked yet.

Step 3: per-model breakdown

The first slice everyone wants: where is the model spend going? Read the month's rollups for one customer and you get a row per meter:

curl "https://api.usagebox.com/api/v1/usage/rollups?month=2026-06&subscription=northwind" \
  -H "x-api-key: $USAGEBOX_KEY"
{
  "month": "2026-06",
  "rollups": [
    { "meter_key": "opus-tokens-doc-summary",   "aggregation": "sum", "value": 9120400 },
    { "meter_key": "sonnet-tokens-chat",        "aggregation": "sum", "value": 7401200 },
    { "meter_key": "haiku-tokens-extract",      "aggregation": "sum", "value": 1891300 }
  ]
}

Aggregate the rows by the model prefix in your own code and you have Opus, Sonnet and Haiku side by side. The read is cheap - the rollup is maintained as records land, so nothing is scanned and nothing contends with ingestion.

Step 4: per-feature and per-customer

The same read is the feature view, because the feature is in the meter name too. Group the rows by suffix instead of prefix and doc_summary, chat and extract line up. To scope a single meter across every customer, filter on the meter and drop the subscription:

curl "https://api.usagebox.com/api/v1/usage/rollups?month=2026-06&meter=opus-tokens-doc-summary" \
  -H "x-api-key: $USAGEBOX_KEY"

And for a per-customer cube, loop your own subscription list and call Step 3 once per customer. That is a handful of cheap reads, not a warehouse query, and it gives you the full customer-by-meter grid for the month.

If doc_summary turns out to be two-thirds of an account's tokens and that feature sits on a flat-rate plan, you have just found a margin leak - and you found it because the separation existed in the meter name, not because you built a feature-cost report.

Step 5: margin per account

Cost is quantity times your rate card; revenue is what the plan charges. Both halves are yours - UsageBox holds the quantity:

const rate = {                      // your vendor cost per unit
  "opus-tokens-doc-summary": 0.000015,
  "sonnet-tokens-chat":      0.000003,
  "haiku-tokens-extract":    0.0000008
};
const cost = rollups.reduce((acc, r) => acc + r.value * (rate[r.meter_key] || 0), 0);
const revenue = planPriceUsd(subscriptionKey);   // from your billing system
const marginPct = (revenue - cost) / revenue;

Run that per subscription and sort ascending. The bottom of that list is the conversation you need to have this quarter - an account whose usage grew past the plan it is on. Compare month=2026-05 against month=2026-06 on the same customer and you see whether it is drifting or spiking.

Step 6: keep the meter list honest

A naming convention degrades the moment two collectors disagree about it. Two habits keep it usable: read the live list rather than trusting your memory of it, and make the collector fail loudly when it sends a key that does not exist.

curl "https://api.usagebox.com/api/v1/meters" -H "x-api-key: $USAGEBOX_KEY"
curl "https://api.usagebox.com/api/v1/subscriptions" -H "x-api-key: $USAGEBOX_KEY"

Have your collector resolve its keys from those endpoints at startup and refuse to run against an unknown meter. That single check turns the silent processed: 0 failure into a crash on deploy, which is exactly where you want it.

Production notes before you ship it

  • Separation happens at ingest. Meters, product items and subscriptions are the only axes. A cut you did not create is a cut you cannot make later.
  • Watch the cardinality. Model times feature times token type is a meter explosion. Encode the two axes you will actually report on, not every axis you can imagine.
  • Assert on processed. Every batch, every time. It is the difference between metering and appearing to meter.
  • Rollups are monthly and UTC. Cross-month comparisons are two reads, and a timestamp an hour either side of midnight on the 1st lands in a different bucket.
  • The price book is yours. UsageBox holds quantities. Rates, plan prices and margin thresholds live in your system, where you can change them without a migration.

Kata variations to try

  • Token-type split. Give input and output tokens separate meters and watch the ratio per feature; an output-heavy feature prices very differently from an input-heavy one.
  • Overhead meters. Send retries, evals and cached reads to non-billable meters so your cost-of-goods is a figure you read rather than infer.
  • Plan-fit report. Run Step 5 across every subscription monthly and flag any account whose margin fell below a floor two months running.
  • Meter hygiene job. Diff /api/v1/meters against the keys your collector is configured to send, and alert on either side drifting.

Kata FAQ

Can I attach arbitrary labels to a usage record? No. A record carries subscription, product_item, meter, value and timestamp. Anything you want to group by has to be encoded in one of those keys at ingest.

How many meters is too many? There is no hard rule, but every meter is a name two systems must agree on. Encode the axes you report on and resist the ones you merely might.

Can I get a cross-tab in one call? One call gives you a month of rollups per meter for one subscription, or one meter across subscriptions. The full customer-by-meter grid is a small loop over your subscriptions, which is cheap because each read is pre-aggregated.

What if I got the naming wrong? Start the new meter now and keep reading both; the old rows stay valid for the months they cover. Records are not rewritten, so a rename is additive rather than a migration.

What you just avoided building

In six steps you got per-model, per-feature and per-customer quantities, a margin calculation per account, and a hygiene check that stops a collector from silently metering nothing - out of one pre-aggregated read and a naming convention. Built in-house, that is an aggregation tier that stays consistent with your raw records, monthly buckets that do not shift under you, and idempotent ingest so a retried collector does not inflate the very numbers you are making decisions on. That is the difference between a meter you invoice from and a meter you manage the business with.

Keep reading: Kata #1 (meter a usage event to an invoice line), Kata #2 (live spend caps on real-time usage), and Kata #3 (reconcile a vendor bill against your meter) - plus the usage-based billing guide for the bigger picture.

Key Topics

  • usagebox kata
  • dimensions
  • cost allocation
  • per-customer cost
  • per-model cost
  • unit economics
  • metering API
  • analytics
  • 2026

Related Articles

Explore more articles on similar topics to deepen your understanding of usage-based billing.

UsageBox Kata #1: From Token Event to Invoice Line in 30 Minutes

A hands-on kata: take a raw AI usage event - a chunk of Claude tokens, a tool call, a credit burn - and turn it into a s...

7 min readRead more

UsageBox Kata #2: Live Spend Caps and Real-Time Usage

Catch and cap AI spend before the bill lands. A hands-on kata against the real metering API: read an account month-to-da...

7 min readRead more

Reconcile an AI Vendor Bill Against Your Usage Meter

A hands-on AI billing reconciliation pattern: compare your monthly meter rollups with the provider bill, localize gaps b...

8 min readRead more

Explore More Articles

Discover our complete collection of usage-based billing guides and implementation patterns.

View all articles