Log usage per tenant from day one, even before billing logic exists — retrofitting usage tracking after customers are already active is far more painful than building it in from the start. Enforce limits before the AI call happens, not after, since a rejected request should never have incurred cost in the first place.
This continues from SaaS Architecture Basics for AI Products — that article covers the overall architecture; this one goes deeper on metering and billing specifically.
What to meter
Pick a unit that maps cleanly to your actual cost and is legible to customers — ideally the same unit, though that's not always possible:
| Unit | Maps to cost | Legible to customers | Notes |
|---|---|---|---|
| Tokens | Directly | Poorly — customers don't think in tokens | Precise but requires translation for pricing pages |
| API calls / requests | Loosely — calls vary in size | Well | Simple to communicate, can misrepresent actual cost if request sizes vary a lot |
| "Messages" / "generations" | Loosely | Well | Common for chat products — one user-facing action, regardless of underlying token count |
| Seats | Not directly | Well | Works when usage per seat is roughly consistent; breaks down with heavy-usage outliers |
Most consumer-facing SaaS products meter in a customer-legible unit (messages, generations) while tracking the underlying token cost internally for margin analysis — the customer-facing unit and your internal cost-tracking unit don't have to be the same thing.
Three billing models that actually work
Flat subscription with usage caps
Simplest to understand and sell. Set the cap conservatively enough that typical usage stays under it, and typical usage should cover your actual cost with margin. Works well when usage is fairly predictable per customer.
Usage-based (pay per unit)
Scales naturally with customer value delivered, but makes revenue less predictable and requires clear, real-time usage visibility for customers so bills aren't a surprise. Works well when usage varies widely between customers and a flat cap would either overcharge light users or undercharge heavy ones.
Hybrid — subscription + overage
A flat tier with a usage cap, plus metered overage beyond it. Gives predictable baseline revenue while still capturing value from heavy users. This is the most common model for AI SaaS products because it balances predictability (baseline subscription) with fair cost recovery (overage) — pick this as a reasonable default unless you have a specific reason to prefer a pure model.
Enforcing limits before cost, not after
Check usage against the plan limit before making the AI call, not after:
async function checkUsageLimit(tenantId: string) {
const usage = await getMonthlyUsage(tenantId);
const plan = await getTenantPlan(tenantId);
if (usage.requests >= plan.monthlyLimit) {
throw new UsageLimitExceededError();
}
}
// In the request handler:
await checkUsageLimit(tenantId);
const result = await callModel(input); // only reached if under limit
await recordUsage(tenantId, result.tokensUsed);
A limit check that runs after the AI call has already been made means you've already paid for a request you were going to reject — the check has to gate the call, not follow it.
Handling the race condition
Under concurrent requests, two requests can both pass a usage check before either one records its usage, letting a tenant briefly exceed their limit. For most SaaS products this is an acceptable, bounded risk (worst case: a small number of over-limit requests before the next check catches it) rather than something requiring distributed locking — reserve stricter enforcement (atomic increment-and-check in your database, or a rate-limiting service) for cases where even small overages are commercially significant.
What to show customers
Give customers real-time visibility into their usage against their limit — a simple usage bar or number, updated promptly. Surprise overage charges are one of the most common sources of support tickets and churn in usage-based products; visibility is cheap to build relative to the support cost of not having it.