Meter LLM tokens, not requests - your flat per-request limit is lying
Two users, both at 100 requests. One sent tweets, the other sent novels. Your cost for them differed by 400x. Your limit treated them identically - and your margins noticed.
Requests are not the unit your provider bills you in
OpenAI, Anthropic, and Google all bill you per token, but most apps cap users per request because counting requests is what a naive rate limiter knows how to do. The mismatch is brutal at the edges: a user who pastes a 200-page contract into every prompt costs you hundreds of times more than one asking short questions, yet both consume one unit of their daily allowance. Either you set the request cap low enough to survive the heavy user - and strangle everyone else - or high enough to be generous, and eat the variance. The only way out is to meter the same unit you are billed in.
quantity is the third argument
Vevee events carry a quantity, and limit groups have a unit - count, tokens, seconds, or cents. Define your plan’s limit group as "1,000,000 tokens per month" instead of "1,000 requests per month", and pass the real token count from the provider response. That is the entire change: the response object you already receive contains usage.total_tokens, and it becomes the quantity you report.
const completion = await openai.chat.completions.create({
model: 'gpt-5.2',
messages,
});
await vevee.track(
userId,
'llm_tokens',
completion.usage!.total_tokens, // the quantity - that's it
);The pre-flight problem (you don’t know the count yet)
The obvious objection: you cannot know the token count before the call runs, so how do you gate? Two workable answers. The cheap one - check canUse with an estimate (your average tokens-per-request is a fine estimate; being off by 20% on a million-token budget is noise). The strict one - reserve an estimated quantity up front, then commit with the actual count, and let the reservation absorb the difference. Vevee’s commit accepts the real number, so the counter ends up exact even though the gate ran on a guess.
const r = await vevee.reserve(userId, 'llm_tokens', 4000); // estimate
if (!r.allowed) return paywall();
const completion = await openai.chat.completions.create({ model, messages });
await vevee.commit(r.reservationId!, {
actual: { quantity: completion.usage!.total_tokens }, // true count
});Tiered models without tiered code
Once the unit is tokens, model tiers stop needing separate code paths. Track gpt-5.2 calls as llm.gpt-5.2 and the mini model as llm.gpt-5.2-mini, then let the plan decide: a "premium tokens" group matching llm.gpt-5.2 with a tight quota, and a "total tokens" group matching llm.* with a loose one. One event can count toward both groups at once - that is the point of match rules. Your application code never branches on plan; it reports what happened and the plan interprets it.
What your users see changes too
A token budget reads as fairer than a request cap, because it is. The user sending short prompts discovers they can make ten times more requests than your old limit allowed; the user pasting books discovers why their allowance drains - and the usage endpoint gives them the numbers to see it happening. Fairness you can show in a usage bar converts better than a limit that feels arbitrary, because users upgrade when they understand what they are paying for, not when they are confused about why they were stopped.
Keep reading
The race condition in "if (usage < limit)" that is costing your AI app money
A user at nine of ten images opens six tabs and clicks generate in all of them. Every tab passes your limit check, every tab gets an image, and you pay for all six. The bug is one read-then-write - and your unit tests will never catch it.
MeteringHow to build a credits system for your AI app (ledger design, rollover, refunds)
A user emails: "I had 40 credits this morning and now I have 12, and I only made one image." If your balance is a column you mutated, you cannot answer that email. Here is the ledger design that can.