Your AI Bill Does Not Name a Customer: Three Layers That Fix Attribution

Provider cost reports group by workspace, model and day, never by your customer. The three layers of instrumentation that produce a per-customer number, with the exact fields to send and a worked conversion from one usage row to dollars.

No model provider can tell you which of your customers cost you what, and none of them claim to. Anthropic's cost endpoint groups by workspace and description at daily granularity; its usage endpoint adds API key, model, service tier and context window. None of those is a customer. The only record that ever contains both your customer identifier and the token counts for a call is the response your own code receives, which means per-customer cost is something you instrument, not something you query. Three layers get you there, and all three can be in place before the end of the day.

Split the keys before you split the bill

The coarse layer is free and immediate: one API key per surface, not one key for the company. A key for the customer-facing feature, a key for internal batch jobs, a key for evaluations, a key for the staging environment. On Anthropic, the Usage API filters and groups by api_key_ids[] and workspace_ids[], so this alone separates "customers used the product" from "we ran a backfill" without touching application code.

Two documented holes to know about before you trust the grouping. Usage from the Workbench is not associated with an API key, so api_key_id comes back null; and the default workspace reports a null workspace_id. Create a named workspace for anything you intend to measure rather than leaving traffic in the default.

Separate keys pay off twice, because they are also the blast radius when one leaks. A key scoped to a single surface can be rotated on a Tuesday afternoon, which is not true of the one key that everything shares, and leaked keys have a resale market that does not wait for your rotation schedule.

Tag every request with an opaque customer id

The fine layer is one field. The Messages API takes a metadata object with a user_id string, described as "an external identifier for the user who is associated with the request", and the documentation is explicit about its form: "This should be a uuid, hash value, or other opaque identifier... Do not include any identifying information such as name, email address, or phone number." So send your internal account UUID or a hash of it, never the email address, and keep the mapping on your side.

Set it in the client wrapper, not at each call site. If a single function builds every request, the field is one line and it cannot be forgotten in the feature someone ships next month; if forty call sites build their own requests, you will be chasing the untagged ones for a quarter.

Write your own usage ledger

This is the layer people skip, and it is the one that answers the question. Every response carries a usage object with the token counts for that call. Write a row for every request, synchronously, into your own table: timestamp, customer id, feature name, model, uncached input tokens, cached input tokens, cache creation tokens, output tokens, and any server tool counts such as web_search_requests. Store token counts rather than a computed cost, because prices change and you will want to reprice history.

If you route through OpenRouter, the ledger is easier still: usage accounting is on by default and every response includes token counts, the cost charged to your account and the upstream provider cost, with cached_tokens and cache_write_tokens broken out. For streaming responses the usage block arrives in the final SSE message, which is the single most common reason a ledger comes out empty: the handler returns to the caller before the last chunk lands.

Turning one row into dollars

Take a support assistant with a long cached system prompt: 12,000 cached input tokens, 800 uncached input tokens and 350 output tokens per call, on Claude Sonnet 5. From the published price list, introductory pricing through 31 August 2026 is $2 per million input tokens, $10 per million output, and cache hits at $0.20 per million. Our arithmetic:

  • Cache reads: 12,000 x $0.20 / 1,000,000 = $0.0024
  • Uncached input: 800 x $2 / 1,000,000 = $0.0016
  • Output: 350 x $10 / 1,000,000 = $0.0035
  • Total per call: $0.0075

A customer making 40 calls a day runs 1,200 calls a month, or $9.00. On a $29 plan that is 31% of the revenue in inference alone, before hosting, payment fees or support. Now apply the price step already announced for 1 September 2026, when Sonnet 5 moves to $3 input, $15 output and $0.30 cache reads: the same call costs $0.01125 and the same customer costs $13.50, or 47% of that $29. The customer did nothing different. Nobody without a ledger will notice until the invoice arrives, and even then the invoice will say the model, not the customer.

Two honest caveats. Token counts per call vary with the conversation and the prompt, so treat the shape as the method rather than a benchmark for your product. And the cache read line only exists if caching is actually hitting; a cache write costs 1.25x base input for the 5-minute window and 2x for the hour, so a workload that writes more than it reads is more expensive than the naive version, not less.

Reconcile weekly, and know what will never match

Your ledger is the per-customer truth. The provider is the billing truth. Once a week, sum your ledger by workspace and compare it against the cost report:

GET https://api.anthropic.com/v1/organizations/cost_report
    ?starting_at=...&ending_at=...&group_by[]=workspace_id&group_by[]=description

That endpoint needs an Admin API key, which is a different credential from your normal key, starts with sk-ant-admin01-, and is not available on individual accounts. The cost report is daily granularity only; the usage report supports 1m, 1h and 1d buckets, capped at 1,440, 168 and 31 buckets respectively, which is the constraint that decides how you page a backfill. Data typically appears within five minutes, and the documented sustained polling rate is once per minute.

Expect a residual gap rather than a match, and know its sources so you stop investigating them: Priority Tier costs are not in the cost endpoint at all and have to be tracked through usage; code execution appears in the cost endpoint under the description "Code Execution Usage" and not in the usage endpoint; and any traffic through the Workbench has no API key attached. Write those three down next to the reconciliation job.

What you can do once the numbers exist

The point of the ledger is that it turns pricing arguments into arithmetic. Sort customers by monthly cost and the distribution is almost always long-tailed, which is the fact underneath every flat-price AI product that has had to change its terms. With a per-customer number you can set a fair-use threshold at a percentile you have measured, quote an enterprise deal knowing the floor, or route the cheap 80% of requests to a smaller model and prove the saving instead of estimating it. The price gap between tiers is wide enough that routing is usually the largest single lever, and the open-weight tier sits well below both for work that does not need a flagship.

Seven columns, one metadata field, one weekly job. If you can name the customer sitting at the top of that sorted list by Friday, the instrumentation has already paid for itself, and you will have a better idea of which subscriptions in the rest of your AI stack are earning their keep too.

Discussion

Sign in with Google or just a name. No email link, no password to remember.