How to Track OpenAI and Anthropic API Costs per User (2026)
Your provider bill is one number. How to attribute every LLM call to the user, plan and customer that caused it, with one line of code.
On this page
The OpenAI and Anthropic dashboards tell you what you spent. They do not tell you who spent it. For a product with paying users, that is the question that matters: which customers cost more than they pay, which plan is underpriced, which feature quietly burns half the budget.
This guide shows how to attribute every LLM call to the user, plan and customer behind it, the traps that make cost numbers wrong, and a one-line way to do it.
What "cost per user" actually requires
Three things, stored together on every model call:
- Token counts by type: input, output, cached input read, and cache writes. They are priced differently, and caching can change a bill several times over.
- The model, because prices differ by an order of magnitude between models of the same provider.
- Business context: the user id, and usually the plan and the organization.
If the cost lands in one system and the user id in another, you end up joining logs by timestamp, which breaks the moment two requests overlap. Keep them in the same record.
The do-it-yourself version
The formula is simple:
cost = input_tokens / 1,000,000 * input_price
+ cached_read_tokens / 1,000,000 * cached_read_price
+ cache_write_tokens / 1,000,000 * cache_write_price
+ output_tokens / 1,000,000 * output_price
Read the usage object of each response, multiply by the current price list, and write one row per call with the user id next to it. Then aggregate by user, plan and day.
It works, and it is a good way to understand your costs. The maintenance is the problem: prices change, new models appear, streaming responses report usage differently, and every code path that calls a model has to remember to log.
Traps that make the numbers wrong
- Cache tokens counted as normal input. You will overestimate cost on cached prompts, or underestimate cache writes.
- Streaming without usage. Some streaming modes only report usage when you ask for it, or only in the last chunk.
- Retries and tool loops. A request that retried three times costs three times. Attribute every attempt, not just the one that succeeded.
- Background work with no user. Summaries, embeddings and scheduled jobs still cost money. Tag them with the user or customer they serve, or with a clear system label.
- Personal data as identifiers. You do not need emails in your cost data. Use your internal user id or a hash.
One line with the Voight wrappers
Voight's wrappers for the OpenAI and Anthropic Node SDKs capture every call (prompt, tokens including cache reads and writes, cost, latency, tool calls, errors) without changing how you call the SDK. The fastest way in is the wizard, which detects your providers and writes the wrapped clients for you:
npx -y @voightxyz/sdk init
Or wire it by hand:
import OpenAI from 'openai'
import { wrapOpenAI, withTrace } from '@voightxyz/openai'
const openai = wrapOpenAI(new OpenAI(), { agent: 'production-chat-api', privacy: 'standard' })
app.post('/api/chat', async (req, res) => {
await withTrace(
async () => {
const reply = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: req.body.prompt }],
})
res.json({ reply })
},
{
routeTag: 'POST /api/chat',
tags: { userId: req.user.id, plan: req.user.plan, org: req.user.orgId },
},
)
})
The tags are the one line that matters. Every model call inside withTrace is stamped with them through async context, so you do not pass anything through your call sites. @voightxyz/anthropic works the same way for messages.create.
What you get
- A Users view with spend, traces, tokens and tool use per user, filled as soon as the first tagged request lands.
- A Models view with cost and token mix by model, ranked, and a Tools view with how often each function is called and how long it takes.
- The route tag (
POST /api/chat) on every trace, so an expensive request is easy to place. - Traces for each request, with every model call inside it, so an expensive user can be explained call by call.
- Your ids, not your users' data. Voight stores the tag as a string. It never sees the user record behind it.
Your provider keys stay in your app's environment; the wrappers report usage to Voight, they do not proxy your traffic.
Questions this answers
| Question | Where it shows up |
|---|---|
| Which customers cost more than they pay? | Users view, sorted by spend, next to their plan tag |
| Which model is eating the budget? | Models view, cost by model ranked |
| Why did this user cost $40 yesterday? | Their traces, call by call |
| Is caching working? | Token mix per model: cache reads versus fresh input |
| Which tool calls are slow or failing? | Tools view: frequency, success rate and duration |