Blog

How to Track LLM API Usage and Cost by Feature, Team, and Customer

A provider invoice tells you what was spent, not who spent it. Two ledgers and five attribution dimensions make every request traceable to a feature, a team, and a customer.

How to Track LLM API Usage and Cost by Feature, Team, and Customer

Your provider sends an invoice. You recognize the total, roughly, and then someone asks the question that actually matters: which feature, which team, which customer? If nobody can answer, the invoice is not the problem. LLM API cost attribution fails for a simpler reason: the calls your application made were never recorded with enough context to split the total into parts.

Attribution starts working when two things are true. The provider's side and your side both keep a ledger, and both ledgers can be joined on a shared request identifier. This guide sets that up: what each ledger contains, the five dimensions to attach to every request, the minimum event table to store them in, and the traps that open gaps between what you logged and what you were billed.

Why the invoice total answers almost nothing

Provider dashboards speak provider vocabulary. Organization, project, API key, model. These are billing objects. The questions you get asked are product objects. Is the summarization feature getting more expensive per active user? Does this tenant's consumption still fit their plan? Can the internal tools team move its workload to a lighter model without anyone noticing?

AI usage visibility lives or dies at that seam. No amount of staring at the total closes the gap, and no dashboard built on the same billing data will either, because the missing context exists at exactly one moment: inside your application, when a request is made. That is when the code still knows which user is waiting, which feature triggered the call, and which customer is paying for it. Capture it then, or guess later.

The two ledgers behind LLM API cost attribution

Cost attribution needs two ledgers, because the two sides know different things.

The provider-side ledger is your reconciliation source: what was actually counted and billed. How you read it depends on the provider.

OpenAI publishes organization-level Usage and Costs APIs as part of its Administration endpoints, and the documentation is explicit about credentials: use a standard API key for application requests, but an Admin API key for Administration endpoints (Usage API reference, Costs API reference). The usage endpoints return time-bucketed aggregates that you can filter and group, including by model, project, API key, and user.

Anthropic exposes a messages usage report in its Admin API, with grouping dimensions that include account, API key, workspace, and model, and a selectable time granularity of day, hour, or minute buckets. The Console's usage page can also export usage data by API key and model, which is often the fastest way to get a first reconciliation running.

If you consume models through a platform such as String AI, the provider side is closer than you think: the usage records page shows the detailed consumption of every call, and the platform documentation lists what drives that consumption, including the model used, input length, output length, whether files were uploaded, and conversation history length. That list matters, because it tells you which request fields are worth logging on your side.

The application-side ledger is one row per model call, written by your code: when it happened, which model served it, tokens in and out, how it ended, how long it took, and the attribution labels only your application can know. Providers cannot name your features or your customers. That vocabulary lives here.

The join between the two ledgers is a request identifier. Services typically return a request ID with each response, and logging it is standard operational practice; write it on every application event so any line in your ledger can be traced back to the provider's view of the same call. One dose of realism about reconciliation: the ledgers will not match exactly, and they are not supposed to. Aggregate windows differ, cached calls collapse, and batch work lands in different time buckets than it was submitted. The goal is to explain the differences on a schedule, not to force the numbers to agree.

Five dimensions to attach at the call site

Every request should carry five attribution dimensions. None are expensive to add; all are nearly impossible to add retroactively.

  1. User ID. Which person was waiting on the response. If you want to track token usage per user, this is the only honest way to do it; a shared API key hides exactly the detail you need.
  2. Tenant or account. The customer, or the internal team, that the work belongs to. This is the dimension that turns a usage bill into a chargeback, or at least a showback.
  3. Feature. The product capability that triggered the call: onboarding assistant, nightly digest, search reranking, whatever your team already calls it out loud.
  4. Route or model. Which model actually served the request, after fallbacks and routing had their say. Keep it separate from the feature label; features and models are many-to-many, and collapsing the two loses the ability to answer either question well.
  5. Environment. Production, staging, or a developer's experiment. Untagged test traffic is one of the classic ways a usage report acquires an unexplainable baseline.

The important part is where the labels are attached: at the call site, in code, as required parameters of your model client wrapper. Labels added at log-reading time are reconstructions, and reconstructions from message text are guesswork. One rule keeps this honest: if a call cannot name its user, tenant, and feature, that is a bug, not a data point.

The minimum event table, and three views that earn their keep

Start with a raw events table, one row per model call, before you think about dashboards. The minimum column set that answers most questions:

ColumnWhat it holds
timestampwhen the call was made
request_idthe provider's identifier, when one is returned
user_idwho was waiting
tenantcustomer or team
featureproduct label
modelwhat actually served it
input_tokens, output_tokensusage as reported for the call
statuscompleted, failed, retried, or cancelled
latency_mshow long it took, recorded uniformly

Two structural notes. First, store raw events and build aggregates on top of them. Aggregating at write time bakes today's questions into the schema, and the questions will change: by feature becomes by customer becomes by feature and model together. Second, keep status and timing in the same table as usage, because cost questions rarely arrive alone; they arrive attached to reliability questions.

On top of the raw table, three views pay for themselves:

The feature view groups usage and cost over time by feature. Usage tracking per feature catches the pattern that matters most, a capability growing faster than its user base, while it is still a trend line instead of a quarterly surprise.

The customer view splits consumption by tenant. This is the view finance and support both want: one team to check whether usage tracks the contract, the other to answer why an account looks different without waiting on engineering.

The model view shows which models serve which features, and in what mix. Multi-model cost tracking starts here, because different models carry different pricing, so a change in the mix explains cost moves that have nothing to do with usage volume, and the reverse.

If a view has nobody who would look at it, do not build it yet. Attribution infrastructure has a way of growing dashboards nobody reads; start with the three questions you already get asked.

Four traps between your logs and the invoice

Streaming usage arrives late. When responses stream, token counts typically become final only when the stream ends. A logger that writes the record at the first byte records an empty usage for exactly the calls users care about most. Write the record at stream end, or update it when the final usage event arrives.

Failures and retries are usage too. A failed call can still consume input tokens, and retry logic multiplies everything you send. A ledger that records only successes will show a gap against the provider's count, and the gap will look like someone else's mistake. Record every attempt with its status, and mark which attempt eventually succeeded.

One feature, several models. Fallbacks, tiered routing, regional variants, and multi-provider setups scatter a feature's usage across model rows. If you group by model first, the feature you are investigating disappears into fragments. Aggregate by feature, and keep model as a column rather than the primary key.

Caching and batch change the timing on purpose. Cached responses mean the provider counts fewer calls than your code made. Batch work counts on the provider's schedule, not yours. Both create differences that are correct. Reconcile on a schedule, explain the deltas, and keep the explanations. The moment you "fix" the differences by adjusting logs, both ledgers become fiction.

From attribution to billing, quotas, and promises

Attribution data is the precondition for anything you want to promise: credits, plan limits, team allowances, customer-facing usage reports. If you cannot say what a customer consumed, you cannot cap it, price it, or explain it, and every quota conversation becomes a negotiation conducted with guesses. The seller-side treatment of the same machinery, metering before monetizing, is covered in How to Monetize an API in 2026.

The order matters: instrument first, promise second. Teams that publish quotas before they can measure usage spend the next month negotiating exceptions.

Where to go next

Track first, split later, and start from the first call. Retrofitting attribution onto months of untagged usage is the expensive version of this project.