Skip to content
All posts
By the PromptUnit team····8 min read

LLM Cost Attribution by Feature: How to Tag and Track It

What OpenAI and Anthropic can group AI spend by, what their metadata fields do not do, and how to get cost per feature with one request header.

cost-attributionobservabilityllm-cost-managementapi-tagging

Your provider invoice shows what you spent. It does not show which part of your product spent it. OpenAI and Anthropic group spend by things they know about: model, API key, project or workspace. They know nothing about your features unless each feature has its own key, project or workspace.

You can change a specific behavior, never a monthly total. To decide whether a feature should use a cheaper model, send shorter prompts or cache its system prompt, you need that feature's own token volume and spend. Take a support tool with a ticket summarizer and a reply drafter. The invoice cannot say which one costs more. A feature tag can.

What the providers can group spend by

Both providers split usage by account structure (projects, workspaces, API keys) and by model and service tier. None of the grouping fields in the table is the metadata field you send with a request.

Source Can group by (main fields) Per-feature option
OpenAI usage endpoint project, API key, model, batch, service tier One project or key per feature
OpenAI costs endpoint project, API key, line item One project or key per feature
Anthropic usage report API key, workspace, model, service tier, context window One workspace per feature, or one key (tokens only)
Anthropic cost report workspace, description One workspace per feature
x-promptunit-feature header the name you send The header value, the same on every supported provider

OpenAI

OpenAI organizes API access into projects. Each project has its own API keys and rate limits, and OpenAI's production guidance says you can set spend limits per project. The same structure works for features: one project per product area.

For tagging a request, OpenAI offers metadata: up to 16 key-value pairs, with keys up to 64 characters and values up to 512. OpenAI describes it as a way to find objects through the API or the dashboard.

The older user field is marked deprecated in OpenAI's API reference, which points to safety_identifier and prompt_cache_key instead. OpenAI's safety guidance says to hash usernames or email addresses before sending them as a safety_identifier. For GPT-5.6 and later, the prompt caching guide describes separate prompt_cache_key values as a way to keep cache accounting apart for each customer, which it says helps explain cached token usage and billing. Neither safety_identifier nor prompt_cache_key is a grouping option in the usage or costs endpoints.

Anthropic

Anthropic's equivalent of a project is the workspace. Each workspace has its own API keys, spend limit and rate limits, and an organization can have up to 100 by default. The cost report groups only by workspace and description, so one key per feature gives you tokens but not dollars. The default workspace cannot have its own limits and shows a null workspace_id, so give each feature a named workspace.

These reports need Admin API credentials, and individual accounts cannot use the Admin API. API responses include an anthropic-workspace-id header you can log to match your own records to the reports.

The Messages API metadata object takes one field, user_id, up to 512 characters. Anthropic asks for an opaque value such as a UUID or a hash, never a name, email address or phone number.

See what each request costs

PromptUnit shows every request your app sends: the model it asked for, the model that answered, what it cost and what it saved. You change two settings, and your own provider key stays.

Try it free →

When separate keys, projects or workspaces are enough

One key, project or workspace per feature gives you per-feature numbers from the provider's own reports, with no logging code of your own. It stops working when two features share a key, when one feature runs in several places you want to compare, or when you want to split a feature by plan or experiment variant. At that point, tag the request instead.

Tag requests yourself

The other approach is to attach a feature name to every request and store it next to the cost. You can write one log record per call in your own code, or tag at a proxy that sees every call.

What to log

  • The feature name.
  • The model you asked for and the model that answered.
  • Input, output and cached token counts from the response.
  • A hashed or random user id, never an email address, if you need per-user numbers. AI cost per user shows how to turn that into cost per monthly active user.
  • The user's plan and any experiment variant.
  • A session id for multi-turn features.

Name features after product behavior, such as ticket-summary or search-rerank, and keep the list stable, because a tag that changes every sprint cannot be compared week to week. An agent loop makes several calls for one user action, so tag each kind of step. The multi-agent cost spiral explains why those calls add up.

Where token counts mislead

Token counts are not dollars: multiply by the model's current price, or reconcile against the provider's cost report. Two details to watch:

  • OpenAI streams. A streamed chat completion includes usage only if you set stream_options with include_usage, and a stream that is cut off or cancelled may never deliver that last chunk.
  • Anthropic cached input. input_tokens counts only tokens after the last cache breakpoint. Total input is cache_read_input_tokens plus cache_creation_input_tokens plus input_tokens.

Tagging with one header in PromptUnit

PromptUnit is a proxy. You point your app's AI API base URL at it, it checks each request, and it uses a cheaper model when that model is just as good. Otherwise the request goes to the model you asked for. It works with OpenAI, Anthropic, Google, Groq and DeepSeek keys and with your own OpenRouter key. Prompts are never stored, only metadata.

Send the header

Add x-promptunit-feature to each request. The OpenAI Python SDK can set a header on the whole client with default_headers, or on one call with extra_headers:

from openai import OpenAI

client = OpenAI(
    api_key="your-openai-key",
    base_url="https://www.promptunit.ai/api/proxy/openai",
    default_headers={"x-promptunit-key": "pu_live_xxxxxxxxxxxx"},
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarize this support ticket: ..."}],
    extra_headers={"x-promptunit-feature": "ticket-summary"},
)

PromptUnit keeps ASCII letters, digits, underscore, hyphen and dot, removes every other character and cuts the value at 64 characters. A name with a space loses the space, so customer support is stored as customersupport. Use hyphens.

What the dashboard shows

The dashboard lists every request with the model your app asked for, the model that answered, the cost and the saving. The Analytics page has a Cost by feature list with the spend for each name. Depending on your account type, the main dashboard also has a Feature Attribution table with calls, spend, amount saved and percentage saved for each name, largest spend first, and requests sent without the header show up there as untagged.

Your app's steps

If you cannot tag every call, the dashboard also has a panel called Your app's steps. It groups the last seven days of requests into the kinds of request your app sends, based on the instructions each request carries, the model it asks for and the feature header, and shows cost and saving for each step.

When a step stayed on the model your app asked for, the panel gives the reason in plain words, such as that the step uses tools, checks or grades work, or sends images or files. It lists up to eight steps, and requests with neither instructions nor a feature header land under Other requests.

Limits to know

In Privacy mode PromptUnit does not store feature names or steps, so they cannot appear in these views. PromptUnit has no per-user view, so keep per-user cost in your own logs. PromptUnit is free until it has saved you money, then it takes 20% of the savings.

A weekly review that uses the tags

Once requests carry a feature name, a weekly review takes a few minutes: pull spend, tokens per call and call count by feature for the past seven days, sort by spend and read the top three. Ask three questions about each.

  1. Is this model right for the task? A feature that only classifies or extracts fields may be a candidate for a cheaper model, but test on your own traffic first. The GPT-4o-mini quality analysis shows where the smaller tier holds up and where it breaks, and the six-fixes post has a GPT-4o vs GPT-4o-mini decision rule.
  2. Is the average token count reasonable? A system prompt that grew, extra history or unbounded output shows up as a high count on one feature.
  3. Is there a caching opportunity? A large system prompt that stays the same across calls is a candidate for provider-side prompt caching. Prompt caching on OpenAI and Anthropic covers how each provider handles cached input.

Then set an alert or a budget per feature. LLM Cost Tracking: The Complete Guide covers alert types and budget caps, and the observability metrics guide shows where attribution sits among the other metrics worth tracking.

Start with the cheapest split that gives you real numbers, and add dimensions only when a question forces you to.

Current prices of the models in this post

Per 1 million tokens, input and output, read every day from each provider's own pricing page.

All AI API prices →

FAQ

Can OpenAI or Anthropic metadata fields split AI cost by feature?

Not in the provider's reports. Anthropic's metadata accepts only a user_id, and OpenAI's metadata is for finding objects through the API or dashboard. Neither is a field the usage or cost reports group by. Use one project or workspace per feature, or tag requests yourself.

What can the x-promptunit-feature header contain?

ASCII letters, digits, underscore, hyphen and dot, up to 64 characters. PromptUnit removes every other character, so customer support is stored as customersupport. Use customer-support.

Do I need the feature header to see my app's steps?

No. PromptUnit also groups requests into steps using the instructions they carry. Requests with neither instructions nor a header go under Other requests.

Start saving from your first request

Change two settings and see what your first request saved within seconds. Free until we've saved you money. If we save you $0, you pay $0.

Get started free →