Originally published on Extenify.
LLM cost per user is the number that tells you whether your AI feature earns money or quietly burns it. Your OpenAI, Claude or Gemini invoice shows the monthly total, but not that one user ran 4,000 summaries while the median user ran 30. Every response already reports its token usage, so you can build the per-user view yourself.
Provider reporting follows your account structure. Anthropic's Usage and Cost Admin API, for example, groups spend by API key, workspace, model and service tier. That helps you check an invoice, but a typical AI extension or SaaS backend sends every user's request through one server and one key.
The per-user fields that do exist are for abuse detection, not billing:
safety_identifier helps OpenAI spot users who break its usage policies.metadata.user_id is an external user ID Anthropic may use to detect abuse.
Send a hashed ID in both, but don't expect a cost report from them. The answer is the same one developers landed on back in 2023: log the user, model and token counts for each call in your own database.
Each provider returns a usage object, but "input" means something different in each:
| OpenAI (Responses) | Claude (Messages) | Gemini (generateContent) | |
|---|---|---|---|
| Input field | input_tokens |
input_tokens |
promptTokenCount |
| Includes cached tokens? | Yes | No | Yes |
| Cache reads | input_tokens_details.cached_tokens |
cache_read_input_tokens |
cachedContentTokenCount |
| Reasoning | inside output_tokens |
inside output_tokens |
thoughtsTokenCount , separate |
Two consequences:
input_tokens minus cache reads and cache writes. For Claude, total input is cache reads plus cache writes plus thoughtsTokenCount to the output count yourself.
A compact normalizer that maps everything into one shape:
const n = (v) => v ?? 0;
export function toTokens(provider, u) {
if (provider === 'anthropic') {
return { fresh: u.input_tokens, cacheRead: n(u.cache_read_input_tokens),
cacheWrite: n(u.cache_creation_input_tokens), out: u.output_tokens };
}
if (provider === 'openai') {
const read = n(u.input_tokens_details?.cached_tokens);
const write = n(u.input_tokens_details?.cache_write_tokens);
return { fresh: u.input_tokens - read - write, cacheRead: read, cacheWrite: write, out: u.output_tokens };
}
// gemini
const read = n(u.cachedContentTokenCount);
return { fresh: u.promptTokenCount - read, cacheRead: read, cacheWrite: 0,
out: n(u.candidatesTokenCount) + n(u.thoughtsTokenCount) };
}
Streaming is covered too. OpenAI puts usage on the response.completed event, Claude's message_delta counts are cumulative (keep the last one, don't add them), and Gemini's streamed chunks carry usageMetadata (keep the last). Write the record in a finally block so a cancelled stream still gets charged to the right user.
Token counts only turn into dollars through a price table, and two details matter:
Store the result as integer micro-dollars per request. Summing millions of floats drifts; summing integers doesn't.
Here's why per-request pricing matters. Take a page summary with 6,000 input tokens (4,000 cached) and 500 output tokens. At October 2026 prices it costs about $0.0094 on gpt-6.1-sol and about $0.0037 on Gemini 3.8 Flash. At 40 a day for 30 days, that one user costs roughly $11.28 a month on the first model and $4.41 on the second, until the Gemini price doubles in January. If your plan is $5, that line is your whole margin.
Each row needs: a server-verified user ID (never a value the extension sends, since anyone can edit extension code), feature name, provider, model, timestamp, normalized tokens, cost in micro-dollars, and status (completed, incomplete, cancelled). Index on { userId, at }.
From there, a monthly group-by-user followed by a percentile step (MongoDB 7.0+ has $percentile) gives you what you need. Read the results in this order:
Request quotas protect you from bursts, not from cost: one 200,000-token page costs far more than a short paragraph. Before each call, check the user's month-to-date spend against a dollar limit (at scale, keep a running counter instead of summing every time). Then act on what the ledger shows: send simple tasks to cheaper models, cache shared system prompts, alert on spikes, and add a new price period the day a price change is announced.
Once a month, compare ledger totals with each provider's own reporting. A gap of more than a few percent usually means a code path, such as a background job, that calls the model without writing a row.
The full guide to tracking LLM cost per user across OpenAI, Claude and Gemini includes the complete price table, ledger code and MongoDB aggregation. And if you want the other half of the unit economics, weekly users and ratings for your extension, Extenify's browser extension analytics dashboard tracks them daily on Chrome, Edge and Firefox.
What does your AI feature's p99 user cost compared with the median, and what did you change once you found out?