Qora API — AI API Gateway for Developers

AI API Gateway for Developers

One clear API workflow for your apps, scripts and automations.

Metering and Billing AI Usage Per User: A Practical SaaS Guide

Cover image titled Metering AI Usage Per User with the subtitle Token accounting and per-seat billing, tagged Metering, Billing and SaaS.

Metering AI usage per user means recording the token counts returned by every model call, tagging each record with the user, feature, and organization that caused it, and enforcing quotas from that same ledger. Requests are the wrong unit: providers bill tokens — including cached and reasoning tokens — and if you bill requests, a single heavy user can erase your gross margin without ever tripping a limit.

This guide covers the accounting model, the instrumentation, and the quota and pricing decisions. It is written for a team that already ships AI features and is now adding a billing line for them.

Why per-user AI metering is hard

If an AI call were a normal API call, you would count invocations, multiply by a unit price, and be done. Five properties of model inference break that model.

  • Cost varies by two orders of magnitude per request. A 200-token classification and a 30,000-token document summary are both “one request.” Metering on requests gives every user the same bill and gives your heaviest tenant a subsidy.
  • Usage arrives at the end — or not at all. With streaming, token counts typically appear only in the final chunk. If the client disconnects mid-stream, you have already been billed for generated tokens you never received and, unless you handle it, never recorded.
  • Cached and generated input are priced differently. Prompt caching splits your input tokens into a cached prefix and a fresh remainder, often at very different rates. A schema with a single input_tokens field cannot represent that split, so it cannot be billed or reconciled accurately.
  • Reasoning tokens are billed but invisible. Reasoning-capable models may generate thousands of thinking tokens that the user never sees, billed at the output rate. Without a separate counter, your per-message cost is unpredictable and your margin is a surprise.
  • One user action is not one API call. A single chat turn can trigger retrieval, a router model, a tool-calling loop, and a final synthesis pass — 5 to 20 calls across several models. Retries and timeouts add more, and a timeout after 3,000 output tokens still costs money.

The practical test: if you cannot answer “what did user X cost us last month, broken down by feature?” with one query, you do not have metering — you have a provider invoice and a guess.

What to count: a token taxonomy

A ledger that stores only input_tokens and output_tokens will be wrong within a quarter. Count these classes separately, because they behave differently on both sides of the transaction.

Token classWhere it appearsProvider bills itBill the userThe trap
Input / promptusage.prompt_tokensYes, at the input rateYesIncludes full conversation history — long chats grow super-linearly, not linearly
Cached inputprompt_tokens_details.cached_tokensYes, at a discountYes, at your discounted rateBilling cached tokens at the full input rate silently overcharges and inflates reported margin
Output / completionusage.completion_tokensYes, typically 2–4× the input rateYes, at the output rateOutput dominates chat cost; a short prompt with a 2,000-token answer is not cheap
Reasoning / thinkingcompletion_tokens_details.reasoning_tokensYes, as outputYesNever shown to the user, so nobody notices it in testing; cap it explicitly
Tool / function-call tokensFolded into input + output per stepYesYesMeter per model call, not per user message, or agent features look 10× cheaper than they are
Embedding tokensusage.prompt_tokens on the embeddings endpointYes, input-only rateYesIngestion runs offline, so it never passes through your request middleware
Non-token unitsTiles, seconds, characters (images, audio)YesYesNot tokens at all — keep a parallel unit column or the ledger cannot sum a mixed account
Retries and failed callsYour own logsPartly — output generated before a timeout is billedNoNever charge a user for your retry; absorb it and alert on the retry rate instead

Note that “provider bills it” and “bill the user” are different sets, and the gap is your risk. Retries and aborted streams are billed to you but should never reach a customer’s invoice. Cached tokens are billed to you at a discount and should be passed through at that same discount — pocketing the difference looks like margin until a customer reconciles your usage page against their own logs.

Instrumentation: capture usage on every call

There is exactly one reliable place to capture usage: the code path that receives the provider response. Do not reconstruct it downstream from logs, and do not estimate it with a tokenizer in production — tokenizers drift with new model families and cost CPU on the hot path. Read the usage object the provider returns, tag it, and append it to an event sink.

import time, uuid
from contextvars import ContextVar

# Set once per inbound request by your auth middleware. Never read user_id
# from a request body — a client could otherwise bill someone else.
attribution = ContextVar("attribution")

def meter(response, *, model, feature, latency_ms, sink):
    """Extract usage from one provider response and emit one metering event."""
    usage = getattr(response, "usage", None)
    if usage is None:                       # errors and some proxies omit usage
        return None
    ctx = attribution.get()
    details = getattr(usage, "prompt_tokens_details", None) or {}
    out_details = getattr(usage, "completion_tokens_details", None) or {}
    event = {
        "event_id": str(uuid.uuid4()),      # idempotency key: the sink must dedupe on this
        "request_id": ctx["request_id"],    # same id for every step of one user action
        "user_id": ctx["user_id"],
        "org_id": ctx["org_id"],
        "feature": feature,                 # closed enum you own: "chat", "summarize", "agent_step"
        "model": model,
        "input_tokens": usage.prompt_tokens,
        "cached_input_tokens": details.get("cached_tokens", 0),
        "output_tokens": usage.completion_tokens,
        "reasoning_tokens": out_details.get("reasoning_tokens", 0),
        "latency_ms": latency_ms,
        "occurred_at": time.time(),
        # Raw counts only. No dollars here — see "store tokens, price at read time" below.
    }
    sink.write(event)                       # append-only; never mutate a past event
    return event

def metered_call(user_id, org_id, feature, messages, model, client, sink, **kw):
    token = attribution.set(
        {"request_id": str(uuid.uuid4()), "user_id": user_id, "org_id": org_id}
    )
    started = time.monotonic()
    try:
        resp = client.chat.completions.create(model=model, messages=messages, **kw)
        meter(resp, model=model, feature=feature, sink=sink,
              latency_ms=int((time.monotonic() - started) * 1000))
        return resp
    finally:
        attribution.reset(token)

Four details decide whether this holds up at scale:

  • Streaming needs an explicit flag. Send stream_options={"include_usage": True}; the final chunk carries the usage object with an empty choices array. Without it, streamed requests record zero tokens — the single most common metering bug.
  • Emit asynchronously. Write to a queue or buffer, never block the response path on the sink. Metering rows are tiny compared to request logs, so do not sample — sampling makes per-user invoices wrong precisely at the volume where you need them. The techniques in our LLM observability guide apply here.
  • Deduplicate on event_id. Your own retry logic, a queue redelivery, or an at-least-once sink will duplicate events. A unique key plus an upsert is cheaper than reconciling a doubled invoice later.
  • Record failed calls too. An error event with zero tokens is still evidence — it tells you whether a user is hammering a broken feature or whether a provider is degrading.

Attribution: rolling usage up to user, feature, and org

Attribution is a context-propagation problem, not a database problem. If the right dimensions are not stamped at the moment of the call, no amount of post-processing recovers them. Five rules make it work:

  • Resolve identity once, at the edge. Auth middleware sets user_id and org_id in a request-scoped context. Everything downstream reads it. Accepting a user id from a payload turns your metering into a spoofable API.
  • Make feature a closed enum you own. Use chat, summarize, agent_step — not model names and not free-text tags. Models change quarterly; features do not, and feature-level cost is the number that drives product decisions.
  • Propagate a parent request id through fan-out. An agent turn that makes 15 calls should produce 15 rows sharing one request_id. That gives you a billable unit for pricing and a trace for debugging without sacrificing granularity.
  • Decide how async work is attributed. A nightly re-index belongs to the organization, not to whichever user’s action queued it. Pick that rule once and apply it everywhere, or your per-user totals will double-count background work.
  • Store tokens, price at read time. Never denormalize a dollar amount into the event. Provider rates change, your markup changes, and cached-token discounts change — if the currency is baked into the row, you can never restate history without rewriting it.

Two roll-ups pay for the whole system on day one. Cost per user per day is an anomaly detector: the top ten users by spend will show you a runaway loop, a prompt that grows unbounded, or an abuse case before your provider invoice does. Cost per feature per day is a product signal — it tells you which feature is worth its inference bill and which one to route to a cheaper tier, which is the core move in our guide to reduce AI API costs.

Quotas and throttling: enforce limits before the invoice

The classic failure is checking the balance after the call returns. By then the money is spent. Because you cannot know the exact output token count in advance, quota enforcement needs a two-phase pattern: reserve, then settle. Before the call, reserve an estimate against the user’s remaining budget; after the response, write the actual usage event and release the difference. A simple, defensible reservation is max_tokens × your most expensive rate for that tier. Over-reserving frustrates legitimate heavy users, under-reserving lets them overshoot by exactly one call — so err on the side of one call.

PolicyEnforced atUser experienceUse it when
Hard monthly capPre-flight reservationBlocked until reset or upgradePrepaid credits and free tiers
Soft cap + alertAsync, on the ledgerEmail or in-app banner, service continuesEnterprise accounts where a hard stop is worse than an overage
Requests per minuteGateway / edge, per key429 with Retry-AfterProtecting shared capacity from one runaway script
Token budget per requestmax_tokens on the callShorter answers, no errorCheapest control you have — set it everywhere by default
Concurrency capScheduler / semaphoreQueued work, slower responsesBatch and agent workloads that would starve interactive users
Prepaid credit balancePre-flight, from the ledgerTop-up promptSelf-serve plans where you carry the payment risk

When a limit trips, return the right status. 429 means “you are going too fast, retry later” and must include Retry-After plus a machine-readable body naming the limit and its reset time. 402 means “you are out of credit, top up.” Conflating them means well-written clients either retry forever against an empty wallet or give up on a limit that clears in ten seconds. The retry semantics and backoff behavior are covered in our 429 and rate-limit handling guide. One more rule: compute quotas from the same event ledger as billing. Two sources of truth — a Redis counter for limits and a warehouse table for invoices — always drift, and the drift always shows up as a support ticket.

Billing models: seat, usage, credits, and hybrid

There are only three shapes, and the choice is driven by how much usage varies between your customers — not by what your competitors publish.

  • Seat-only. Simplest to sell and forecast. Correct only when the spread between your p50 and p95 user is under about 2×. The moment one tenant runs a batch job, a flat seat price converts your best customer into your worst-margin customer.
  • Pure usage / credits. You define an internal credit unit and convert it to tokens at a published ratio. Margin is predictable, but the ratio must stay stable when your provider rates move — if a rate change visibly repriced credits, customers read it as a price increase, so absorb small changes and reprice deliberately.
  • Hybrid (the B2B default). A seat fee includes a committed token allowance, and usage beyond it bills at the usage rate. Size the allowance from real data, not from the sales conversation.

Three decision rules keep a hybrid plan from leaking margin. First, price the seat so the included allowance costs you at most 25–35% of the seat price at p90 usage; anything higher and one power user holds your gross margin hostage. Second, treat prepaid credit breakage as margin only up to roughly a fifth of sold credits — beyond that, customers feel cheated rather than forgetful. Third, never refund tokens you have already paid a provider for; refund the credit instead, and let the usage ledger show why.

Finally, surface the number in the product. A per-user usage page showing tokens, cost, and consuming features removes more billing tickets than any email you can send, and it turns metering into a retention feature instead of a back-office cost.

A gateway that meters for you

Everything above assumes you own the request path and can inspect every provider response. If your calls already flow through an AI API relay, most of the plumbing is a byproduct: qoraapi.com meters usage per API key, so each key becomes a metering boundary you can map to a user, a team, or a tenant — with token counts recorded on the relay side rather than reconstructed in your application.

That moves a specific set of work off your plate: token extraction for every provider and model family, streaming usage capture, retry and error accounting, and per-key rate limiting. What stays with you is the part only you can define — which key belongs to which user, which feature made the call, and what your pricing policy is. The build reduces to two things: stamp a key per user or tenant, and write a metering event per call with your own attribution context. The broader architecture is covered in our AI API gateway guide.

Frequently asked questions

Should I bill cached input tokens to the user?

Yes, but at the discounted rate you actually pay. Cached tokens are real work that your provider charges for, so excluding them understates usage; charging them at the full input rate overstates it. Track them in a separate column so the pass-through rate is explicit and auditable.

How accurate does per-user metering need to be?

Accurate enough to reconcile to your provider invoice within a small percentage. In practice that means using the provider’s own usage object rather than estimating, recording failures and retries, and deduplicating events. If your monthly total lands within one call of the invoice, the residual is your infrastructure cost, not a billing error.

Do I need a tokenizer to count usage?

No. Every major provider returns exact counts in the response, including for streaming when you request usage explicitly. Keep a tokenizer only as a pre-flight estimator for quota reservations or prompt budgeting, and accept that it will be approximate.

What is the minimum viable metering schema?

One append-only event table with: event_id, occurred_at, request_id, user_id, org_id, feature, model, and the token columns — input, cached input, output, reasoning. That is enough to produce per-user invoices, feature cost reports, and quota checks from a single source of truth. Add a parallel unit column when you start billing images or audio.

Conclusion

Per-user AI billing fails on the accounting model, not on the invoicing UI. Count tokens by class — including cached and reasoning tokens — capture usage from the provider response in middleware that stamps user, feature, and org, store raw counts and price them at read time, and enforce quotas with a reserve-then-settle check that returns 429 with Retry-After. Then choose a billing shape that survives your p90 user. Put a metering gateway in front of the providers and the hard half of that list stops being your code.

Related reading

Build AI features with one clear API

Qora API gives you a single, focused gateway to connect your apps, scripts and automations to AI. Start with one request.

qoraapi.com · AI API gateway for developers

Comments

2 responses to “Metering and Billing AI Usage Per User: A Practical SaaS Guide”

  1. […] the retry rounds have to land in the same ledger as the base run — which is exactly what metering AI usage is for. Without it, a pipeline with a flaky 5% error rate looks 5% more expensive than it is, and […]

  2. […] logs say and what you were billed for is where retries, failed attempts and side costs hide. The usage metering and billing pipeline makes that reconciliation possible, and LLM observability is where per-request counts should […]

Leave a Reply

Your email address will not be published. Required fields are marked *