Skip to content

Cost and FinOpsGuide

Attributing AI cost to customers, features and teams

The provider's finest unit of cost is an account structure you created for its convenience. The dimensions your business runs on — customer, feature, team — only exist where the request passes through something you control.

By INFRO Editorial

Published

Updated

13 min read

A dark operations scene where a single stream of light splits into labelled channels, each carrying a measured share of cost

In short

Provider cost APIs report daily totals grouped by workspace, project or key, and carry no customer-supplied identifier. To attribute AI spend to a customer, a feature or a team you have to record the dimension at the moment the request is made. Put low-cardinality dimensions you will group by into structure — projects, keys, environments — and high-cardinality ones you will filter by into per-request annotations, then track the share of spend you could not attribute at all.

Key takeaways

  • Provider cost APIs report at daily granularity grouped by workspace, project, line item or key — never by an identifier you attached to a request.
  • The end-user identifier providers ask you to send is for abuse detection, not for billing, and is not a grouping in their cost reporting.
  • Decide before instrumenting which dimensions you will group by and which you will filter by; the first belong in account structure, the second in per-request metadata.
  • One key per customer does not scale as an attribution strategy, and providers say so themselves.
  • Requests refused before their body is read, unpriced models and shared prompts are the three places attribution quietly stops being complete.
  • Report the share of spend you could not attribute. It is the number that says whether the rest of the report is worth reading.

A finance partner asks which customer accounted for last month's model spend. The provider console shows a monthly total, a chart by model, and a breakdown by workspace. None of those is a customer. Somebody exports a CSV, joins it to a guess about which key belongs to which team, and produces a number with a caveat attached. The caveat is the honest part, and it is the reason the exercise gets repeated every month instead of answered once.

This is not a reporting failure. It is a consequence of where the cost is recorded. A provider bills the account that authenticated the request, and the account structure is the finest dimension it has. Everything your business actually runs on — a customer, a feature, a team, an agent run, an environment — exists only in the application that made the call, and only at the moment it makes it.

What a provider can and cannot tell you

Both major providers publish organization-level cost reporting, and both are more capable than their consoles suggest. It is worth knowing exactly where they stop.

OpenAI Costs APIAnthropic Cost API
Time granularityDaily buckets only1Daily buckets only2
GroupingLine item, project1Workspace, or description2
CredentialAdmin key, which cannot make ordinary API calls3Admin key; workspace keys are refused2
Customer-supplied identifierNoneNone
Known exclusions—Priority Tier costs are not in the cost endpoint2
Provider cost reporting as documented on 2026-10-13. Both also publish finer usage endpoints that report tokens rather than money.

The usage endpoints go further than the cost endpoints. Anthropic's reports tokens in one-minute, one-hour or daily buckets and can filter and group by API key, workspace, model, service tier, context window, data residency and, in beta, speed2. That is a rich set of dimensions — and every one of them describes the request as the provider saw it, not the work it was doing.

Two details are worth carrying into any design that leans on provider-side reporting. Anthropic notes that usage from the console playground is not associated with an API key, so api_key_id is null when grouping by it, and that costs attributed to the default workspace carry a null workspace_id2. Both are small, and both put a bucket labelled "unknown" in a report somebody will ask about.

The identifier you can send is not the one you get back

Providers do ask you to send an end-user identifier. OpenAI's safety_identifier exists so that abuse can be traced to an individual end user and one user's misuse does not disrupt access for the whole organization4. It is documented for that purpose, and it is not a dimension of cost reporting: OpenAI's costs group by line item and project1.

This trips people up because the field looks exactly like the one they want. Sending it is good practice for the reason OpenAI gives. It is not a cost dimension, and building a chargeback report on the assumption that it will become one is a plan with no source behind it.

Why "one key per customer" stops working

Since keys and workspaces are the dimensions providers do report, the obvious move is to mint one per thing you want to measure. It works at ten customers and fails somewhere before a thousand, for reasons that are structural rather than a matter of effort:

  • Cardinality outruns the reporting. Anthropic's own guidance, answering how to get per-user cost breakdowns, points away from splitting cost across many API keys and suggests tracking token consumption as a cost proxy instead, citing the performance limits of the alternative2.
  • You only get one hierarchy. Customer, feature, team and environment are independent. A key can encode one of them cleanly, two by convention, and the third by parsing its name.
  • Keys carry operational weight. Rate limits, rotation, revocation and blast radius all attach to a key. Choosing how many you have for accounting reasons means making a security decision for a reporting one.
  • The dimension can change after the request. A customer moves to another plan; a feature is renamed; a team is reorganized. A key issued last quarter still says what was true then.

Keys and projects remain the right home for some dimensions. The mistake is treating them as the home for all of them.

Two kinds of dimension, and the question that sorts them

Decide this before instrumenting anything, because it is expensive to change afterwards.

Structural dimensions are low-cardinality, set when a credential or a project is created, and present on every record — including requests that were refused before they reached a provider. Project, environment, provider account and route are structural. You will group by these: they make up the rows of a monthly report.

Annotations are set per request by the application. Customer id, feature, workflow, agent step and experiment arm are annotations. They can be high-cardinality, they change without reissuing anything, and they are only present when the call that carried them was actually made. You will filter by these: they answer a question somebody asked about one thing.

The sorting question is simply: will I want the top ten of this, or will I want the total for one of them? A report that lists every row belongs in structure. A number about one customer belongs in an annotation.

What to attach to a request

The annotation should travel with the call, be recorded next to the cost, and never reach the provider. In INFRO, that is a top-level metadata object on the request body, which is stripped before anything is forwarded upstream because it describes your systems and an upstream has no business receiving it.

an annotated chat request
{
  "model": "anthropic/claude-sonnet-5",
  "messages": [{ "role": "user", "content": "Summarise this ticket." }],
  "metadata": {
    "user_id": "cus_8123",
    "session_id": "sess_2f91ab",
    "feature": "ticket-summary",
    "tags": {
      "team": "support-platform",
      "env": "production"
    }
  }
}

feature is a reserved key with its own indexed column, as are workflow, step and trace_id; requests that cannot carry a JSON body can send them as X-INFRO-Feature, X-INFRO-Workflow, X-INFRO-Step and X-INFRO-Trace-Id headers, and the end customer as X-INFRO-User-Id. A malformed reserved value is refused with 400 invalid_attribution naming the key rather than silently dropped. Anything else goes under tags.

Three conventions matter more than the exact names:

  • Store stable ids, not display names. A customer's name changes and a report written against it silently splits in two. Keep the id; resolve it to a name when the report is rendered.
  • Keep tag keys bounded and agreed. A small, reviewed set of keys is queryable. A set that grows per team produces a metadata document nobody can write a query against.
  • Do not put personal data in an annotation. An opaque customer or account id identifies the right row. An email address turns a request log into a system that needs a different retention policy and a different review.

One practical limit: the multipart transcription endpoint takes its fields as form strings, so a structured metadata object cannot travel with it. Attribute those calls with the X-INFRO-* headers, or by key and project.

Reading it back

Structural dimensions come back as a grouped aggregate. In INFRO, GET /v1/usage groups estimated cost, request counts, token counts and latency percentiles by model, modality, project, key, endpoint, provider kind, connection, route or environment. It also groups by the reserved annotations feature, workflow, step and user, and by up to five tags you promote. Because you choose how many values those have, they return the hundred largest groups by estimated cost plus one row for the rest, and requests that carried no value come back as their own row. The console's Analytics screen offers the same groupings and shows what share of spend carries the chosen field.

spend by project, daily
curl -s "$INFRO_BASE/v1/usage?group_by=project&granularity=day\
&from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z" \
  -H "Authorization: Bearer $INFRO_API_KEY"
spend by feature, this month
curl -s "$INFRO_BASE/v1/usage?group_by=feature&granularity=month\
&from=2026-09-01T00:00:00Z&to=2026-09-30T00:00:00Z" \
  -H "Authorization: Bearer $INFRO_API_KEY"

The request log filters on the metadata you attached — user_id, session_id, feature, workflow, step, and any tag.<key>=<value> pair, all ANDed together.

one customer's traffic on one feature
curl -s "$INFRO_BASE/v1/requests?user_id=cus_8123&feature=ticket-summary&limit=100" \
  -H "Authorization: Bearer $INFRO_API_KEY"

The two together are the weekly review: the aggregate says which rows moved, and the filter says what was inside the row that moved.

Where attribution stops being complete

An attribution system that does not publish its own edges will be believed further than it deserves. There are three edges, and the third is the one that surprises people.

Refusals are recorded, except the earliest ones. When a request is refused before it runs — a budget ceiling, a model policy, no connected provider for the vendor — INFRO still records it, with its key, project, connection and route, the metadata the caller sent, and a cost of zero because nothing ran, so a burst of budget refusals shows up against the feature that caused it. What leaves no record at all is a request refused before the gateway has read which model it asked for: a rate-limit 429, a body that is not JSON, a schema failure. Those show up in your own client logs and nowhere in an attribution report.

An unpriced model is not a free one. When no rate is known for a model, the cost of a served request is recorded as unknown rather than zero, and a bucket reports how many of its requests were unpriced alongside the total. A total over a bucket where some rows are unpriced is not a total, and a report that adds them in as zeros understates exactly the models nobody has priced yet.

Shared cost does not decompose cleanly. A cached system prompt serving five features, a retrieval index built once and queried by everything, an evaluation run before a release: these are real costs with no single owner. The FinOps Framework's allocation capability names this directly — organizations need a shared cost strategy, and many practitioners deliberately keep shared items in a central budget rather than spreading them, an "informed ignore" that is a decision rather than an oversight5.

  1. 1custom_providerYour negotiated rate for this model on this provider kind. The only rung that can be exactly right.
  2. 2customYour rate for this model on any provider.
  3. 3listThe model vendor's published list rate. Right on the vendor's own API, approximate on a cloud reseller.
  4. 4unpricedNo rate on file. Recorded as unknown (null), never guessed and never shown as free.

Per request: the estimate

Computed when the request finishes, from the first rung that has a rate. Attributable to a project, key, route or user.

Per day: the provider's own figure

Where a provider publishes a cost API, the reported daily total is compared with the estimates. The useful output is the variance, not a replacement number.
Where the rate behind an attributed cost comes from. A report is only as good as the rung it was computed on.

Report what you could not attribute

The FinOps Framework lists the ability to surface the percentage of cost that cannot be categorized and allocated as a measure of success for allocation5, because unallocated cost is where accountability disappears. AI spend makes this sharper than cloud spend does. Billing is tied to units of consumption such as tokens processed or API requests made6, so a prompt release can change the bill the day it ships, with no infrastructure change to point at.

Put the unattributed share on the report, every week, as a number:

  • Estimated spend, total, with the number of unpriced requests stated beside it.
  • The top rows by structural dimension: project, key, provider account, route.
  • The top rows by annotation: feature, team, and the customers above a threshold you agreed.
  • Spend that carried no feature tag, as a percentage — with a named owner and a date if it is rising.
  • Shared costs, listed separately and by explicit decision, not spread silently.
  • Variance against what the providers' own cost APIs reported for the same period.

A report whose unattributed share falls quarter on quarter is a report people start making decisions from. One that never mentions it gets quietly discounted the first time somebody checks a row. The same share belongs on the page a finance lead or a CTO reads, stated as plainly as the total; the AI spend report leadership actually reads sets out how.

When the customer id becomes a limit

An annotation recorded for a report can also be enforced. If your product passes its own customers' usage through to your provider accounts, the customer id on the request is the only thing that can stop one customer's loop from spending everyone's month — and it can only do that if it is present when the request is admitted, not added afterwards in a warehouse.

That changes the requirements on the id. A report tolerates a missing value as one more "not attributed" row; a limit has to decide what to do with a request that carries none. Decide it explicitly: serve such requests uncounted, or refuse them so a missing id is found on the first request of an integration rather than at the end of a month. Keep internal traffic with no end customer — batch jobs, evaluations — somewhere the strict rule does not apply.

How INFRO records attribution

Requests run on the provider accounts you connect, those providers invoice you directly, and INFRO charges a subscription with no share of model spend. What the control plane adds is that every request passes through one place that can record the dimension while the call is being made.

  • Structural dimensions on every record. Project, key, environment, provider kind, connection and route are recorded whether the request succeeded, failed upstream or was refused before it ran, once it has named a model.
  • Your annotations, kept out of the provider's hands. A top-level metadata object is recorded on the request and echoed back on its record, and stripped before anything is forwarded upstream.
  • Grouped aggregates and filtered logs from the same rows. GET /v1/usage and GET /v1/requests read the same records, so a chart and a reconciliation cannot disagree. Both are available on any gateway key.
  • The rate that produced the number, stored with it. Each record keeps the estimate as it was priced when the request ran, with its source, rather than being repriced later against a catalog that has since changed.
  • Request history is a plan window. Seven days on Developer, fourteen on Startup, thirty on Team and ninety on Business, enforced on the server. Attribution you need to keep longer belongs in your own warehouse, which is what the usage and request APIs are for.
  • The end-customer id is also a cap. The id in metadata.user_id (or X-INFRO-User-Id) drives a per-customer cost view on every plan and, from the Team plan, end-customer spend caps: one rule per organization and optionally per project, with overrides your backend sets through the management API. A request that would pass its customer's cap is refused before the provider call with 402 and code end_user_budget, carrying resets_at for your app to show its user; a rule can refuse requests with no id with 400 end_user_id_required, or serve them uncounted.
  • Project is a boundary as well as a dimension. An owner or admin can limit a developer or analyst to some projects; they then read those projects' requests, analytics, jobs and findings, and nothing organization-wide. Putting a team's work in its own project is what makes both its report and its access line up.
  • The unattributed share is on the Summary. The console's Summary page shows attribution as one of four status lights, green when at least 80% of spend is assigned to a project and at least 99% of requests are priced. Owners and finance members receive a weekly summary on Mondays and a monthly report on the 2nd by default; a member limited to some projects never receives them, because they describe the whole organization.

See analytics, traces, spend controls, end-customer caps and the Summary for the request shapes and the record fields.

From the team that builds it

Where this shows up in INFRO

INFRO records attribution on the request as it is served, because the account that ran it is the customer's own.

  • Spend attribution

    Private early access

    Group cost by model, project, key, or an end-user id you attach to the request.

    Read the documentation for Spend attribution
  • Usage and cost per request

    Private early access

    Every request records its model, latency, token or unit counts, and estimated provider cost, queryable by project and key.

    Read the documentation for Usage and cost per request
  • Usage, request and FOCUS exports

    Private early access

    GET /v1/usage and GET /v1/requests on any gateway key, the request log and usage series as CSV, plus a FOCUS v1.4 Cost and Usage dataset as JSON or CSV, so AI spend lands in your FinOps platform beside the rest of your cloud cost.

    Read the documentation for Usage, request and FOCUS exports
  • Negotiated provider rates

    Private early access

    Enter the rates your contracts actually carry, per model and optionally per provider, so estimated cost uses them before the vendor's list rate. Included from the Team plan.

    Read the documentation for Negotiated provider rates
  • Request traces and payload logs

    Private early access

    Per-request timing detail, with prompt and completion logging off by default and a retention window you choose.

    Read the documentation for Request traces and payload logs
  • End-customer spend caps

    Private early access

    One spend cap for every end customer of your product, per organization or per project, with overrides your backend sets through the management API. A refused request returns a 402 with the time the cap resets, for your app to show its user. Caps from the Team plan; the per-customer cost view on every plan.

    Read the documentation for End-customer spend caps
  • Project access for members

    Private early access

    An owner or admin limits a developer or analyst to some projects: they read those projects' requests, analytics, jobs and findings and manage the gateway keys in them, and the gateway refuses everything organization-wide.

    Read the documentation for Project access for members
  • Summary, weekly summary and monthly report

    Private early access

    One page for leaders: this budget cycle's estimated provider spend against the budget and its projection, reliability, governance and attribution as four status lights with their reasons, and what needs a decision. Emailed to owners and finance members every Monday, with a printable monthly report on the 2nd; members choose for themselves.

    Read the documentation for Summary, weekly summary and monthly report

INFRO is in private early access. Provider usage is billed to you by your providers; INFRO charges a software subscription and takes 0% of model spend. See plans.

Sources

Numbered in order of first citation. Each was opened and checked against the claim on the date shown.

  1. 1.How to use the Usage API and Cost API to monitor your OpenAI usage (opens in a new tab) — OpenAI. Accessed Oct 13, 2026.Cited in place of the Costs API reference page, which did not resolve on the access date. Shows grouping by line item and project.
  2. 2.Usage and Cost API (opens in a new tab) — Anthropic. Accessed Oct 13, 2026.Cost API grouping, daily-only buckets, the cents-as-decimal-strings format and the Priority Tier exclusion are stated on this page.
  3. 3.Admin API keys (opens in a new tab) — OpenAI. Accessed Oct 13, 2026.
  4. 4.Safety best practices (opens in a new tab) — OpenAI. Accessed Oct 13, 2026.
  5. 5.Allocation — FinOps Framework capability (opens in a new tab) — FinOps Foundation. Accessed Oct 13, 2026.
  6. 6.FinOps for AI Overview (opens in a new tab) — FinOps Foundation. Accessed Oct 13, 2026.

About this article

Written from the published documentation of OpenAI's and Anthropic's usage and cost APIs and the FinOps Foundation's framework pages, each opened on the access date recorded below, and from the request-recording implementation and public documentation of the INFRO gateway, which the authors build. Reviewed on 2026-10-13: every source re-opened, and every INFRO statement re-checked against the docs, the gateway source and the availability registry. Figures in worked examples are illustrative and labelled as such; no customer data was used.

Last checked against its sources on . The next review is due by . No separate named reviewer is recorded for this article.

How articles are written, sourced and corrected is set out in the editorial standards.

Revision history

  1. Corrected OpenAI's cost grouping to line item and project, and the claim that refused requests lose their metadata: they keep it once the gateway has read the body. Added where the end-customer id also becomes a spend cap, how project access follows the same structure, the summary and monthly report's attribution check, and that chat responses now carry price_source.