How to Track Token Usage Per User in Your App
If you're building a product on top of an LLM, you need to know exactly how many tokens each user consumes, not just your total bill at the end of the month. Tracking token usage per user means capturing input and output token counts on every API call, tagging them with a user ID, and storing them somewhere you can query for billing, rate limiting, or cost analysis.
This article walks through the actual mechanics: where token counts come from in the API response, how to structure a database table for per-user usage, and how to turn raw token counts into dollars so you can set limits or bill customers accurately.
Where token counts actually come from
Every response from Claude (and most LLM APIs) includes a usage object with input_tokens and output_tokens. This is the only reliable source of truth — don't estimate tokens client-side with a tokenizer library and call it done, because system prompts, tool definitions, and cached context all affect the real count.
A typical response looks like this:
{
"id": "msg_01XyzAbc",
"model": "claude-sonnet-4",
"usage": {
"input_tokens": 412,
"output_tokens": 187
},
"content": [{ "type": "text", "text": "..." }]
}
If you're using SubToAPI to expose Claude through your own sub_live_... API keys, every response from https://api.subtoapi.app/v1/messages carries the same usage block, plus request-level metadata in your dashboard so you can cross-check counts without building your own logging pipeline from scratch. See /docs/messages for the full response shape.
Step 1: Attach a user ID to every request
The token count means nothing without knowing who generated it. The cleanest way to do this is to pass a user identifier alongside the request and log it at the same time you log the response.
async function callClaude(userId, messages) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
messages
})
});
const data = await res.json();
await logUsage({
userId,
model: data.model,
inputTokens: data.usage.input_tokens,
outputTokens: data.usage.output_tokens,
createdAt: new Date()
});
return data;
}
Don't rely on frontend-supplied user IDs for anything billing-related — resolve the user from your authenticated session server-side before the request ever reaches the model.
Step 2: Store usage in a queryable table
A simple append-only table scales fine for most apps. You don't need to pre-aggregate anything at write time.
CREATE TABLE token_usage (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL,
model TEXT NOT NULL,
input_tokens INT NOT NULL,
output_tokens INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_token_usage_user_date ON token_usage (user_id, created_at);
With this schema you can answer most of the questions that matter:
- Tokens used by a user this billing cycle
- Tokens used per model (useful if you route some requests to a cheaper model)
- Spikes in usage that might indicate abuse or a runaway loop in your app
SELECT
SUM(input_tokens) AS total_input,
SUM(output_tokens) AS total_output
FROM token_usage
WHERE user_id = $1
AND created_at >= date_trunc('month', now());
Step 3: Convert tokens into cost (and limits)
Raw token counts are only useful once you map them to a dollar figure or a quota. Keep a small lookup table of per-model pricing so you're not hardcoding rates across your codebase.
const PRICING = {
"claude-sonnet-4": { input: 3.00, output: 15.00 }, // per 1M tokens
"claude-haiku-4": { input: 0.25, output: 1.25 }
};
function costFor(model, inputTokens, outputTokens) {
const rate = PRICING[model];
return (
(inputTokens / 1_000_000) * rate.input +
(outputTokens / 1_000_000) * rate.output
);
}
Run this against your token_usage rows to get per-user cost, then compare it against whatever plan or credit balance that user has. This is also where you decide what happens when a user hits a limit — block the request, downgrade to a cheaper model, or just flag it for a manual review, depending on your product.
Step 4: Enforce limits before the request, not after
Logging usage after the fact is necessary for billing, but it won't stop a user from blowing through a quota mid-request. For hard limits, check the user's running total before calling the model:
async function checkQuota(userId) {
const used = await getMonthlyUsage(userId);
if (used.cost >= used.plan.limit) {
throw new Error("Token quota exceeded");
}
}
Call this before every model request that counts against a user's quota. It's a cheap check compared to the cost of generating a response you can't bill for.
Step 5: Separate your internal usage from your provider bill
Even with per-user tracking in place, you still need to reconcile your numbers against what your API provider actually charges you. If you're calling Claude directly, you'll need to export usage from your own logs and compare it to your invoice manually. If you're using SubToAPI, usage metadata is already attached to every request and visible per API key in your dashboard, which makes it easier to catch a user ID that's silently generating most of your cost. Team and Scale plans (see /pricing) add per-seat API keys, so you can track usage at the seat level too, not just at the application level.
Avoid these common mistakes
- Counting tokens client-side. Tokenizers differ slightly by model version; always use the
usageobject from the response. - Logging only output tokens. Input tokens — especially with large system prompts or long conversation history — often dominate cost.
- Aggregating at write time. Store raw rows and aggregate with queries; pre-aggregation makes correcting mistakes much harder later.
- Ignoring streaming responses. Usage data for streamed responses typically arrives in the final event of the stream, not the first — see /docs/streaming for the exact event sequence.
If you want to get this working quickly without building the logging and pricing math yourself, /docs/quickstart walks through getting an API key and making your first request, and the usage metadata comes attached by default.
Questions
Do I need a separate database for usage tracking, or can I use my main app database? Your main database is fine for most apps. Usage logging is write-heavy but low-complexity — a single indexed table handles millions of rows without issue. Move to a dedicated analytics store only if usage queries start competing with production traffic.
How do I track usage for streaming responses specifically? The token usage for a streamed response is included in the final SSE event, after all content chunks have been sent. Buffer the stream, capture that final event, and log it the same way you would a non-streaming response. See /docs/streaming for the event format.
Should I track tokens per API key or per user? Track both if you can. Per-user tracking tells you who is costing you money; per-key tracking tells you which part of your system (a feature, a team, an integration) is generating that cost. SubToAPI's dashboard separates usage by key by default, which maps naturally to per-user tracking if you issue one key per user or team seat.