Menu
22 Jun 2026 ยท AI & LLMs ยท 4 min read

Per-User Token Budgets: Controlling LLM Costs in Your SaaS.

Provider dashboards tell you what you spent, not who spent it. How I meter tokens per tenant in Laravel with middleware, soft and hard quotas, budget alerts and graceful degradation.

Provider-side spend limits protect you from one thing: a catastrophic bill. They do nothing about the actual problem in a SaaS product, which is that five percent of your users consume eighty percent of your tokens while paying the same subscription as everyone else. The fix has to live in your application, because only your application knows which tenant a request belongs to. Here is the consumption-control setup I build into client products, with the Laravel version of the code.

Why provider-side limits are not enough

Your API key has one budget at the provider, shared by every customer. When it runs out, everyone is cut off โ€” your best-paying customers included โ€” because one free-tier user found a way to hammer your AI feature. App-side metering inverts that: every tenant has a budget, heavy users hit their ceiling, and everyone else never notices. It also gives you the per-tenant cost data you will eventually need for pricing decisions, which no provider dashboard can offer.

Step 1: meter every call

Every LLM response includes actual token usage โ€” record it, do not estimate it. I keep a single append-only table (tenant_id, feature, model, input_tokens, output_tokens, cost_micros, created_at) plus a fast counter in Redis for the current period, so quota checks never touch the ledger table.

The recording itself belongs in one place, not sprinkled through feature code. A middleware on your AI routes handles the quota gate, and the expensive bookkeeping runs after the response has been sent using Laravel's terminate() hook:

class EnforceTokenBudget
{
    public function handle(Request $request, Closure $next): Response
    {
        $tenant = $request->user()->currentTeam;
        $budget = TokenBudget::for($tenant); // cached per-plan limits

        if ($budget->hardExceeded()) {
            return response()->json([
                'error' => 'ai_quota_exceeded',
                'renews_at' => $budget->renewsAt(),
            ], 429);
        }

        return $next($request);
    }

    public function terminate(Request $request, Response $response): void
    {
        // Usage attached by the AI service after the provider call
        if ($usage = $request->attributes->get('llm_usage')) {
            RecordTokenUsage::dispatch(
                $request->user()->currentTeam->id,
                $usage['feature'],
                $usage['input_tokens'],
                $usage['output_tokens'],
            );
        }
    }
}

The queued RecordTokenUsage job increments the Redis counter and inserts the ledger row. Nothing in the request path waits on a database write, and if the queue hiccups you lose a little accounting precision, never a user request.

Step 2: soft and hard quotas

One limit is a blunt instrument; two limits give you a customer conversation instead of a support ticket. The pattern I use:

  • Soft quota (typically 80% of plan allowance). Nothing is blocked. The user sees an unobtrusive "you've used most of this month's AI allowance" notice, and the account owner gets one email. This converts surprisingly well to plan upgrades โ€” it is a pricing signal, not a punishment.
  • Hard quota (100โ€“120%). The middleware above returns a clean, machine-readable 429 with the renewal date. Set it above 100% for paying plans: a customer mid-task on the last day of the month should finish their task. The overage is pennies; the goodwill is not.

Both thresholds live in the plan configuration, not in code โ€” you will tune them, and marketing will want to change them more often than you deploy.

Step 3: budget alerts for you, not just the user

A scheduled command runs hourly and compares each tenant's month-to-date spend against their trailing average. Two alerts matter: a tenant at 3ร— their normal daily burn (either abuse, a runaway integration, or a genuinely thrilled customer โ€” all three are worth knowing about the same day), and aggregate spend across all tenants against your monthly provider budget. The same numbers feed a small internal dashboard: cost per tenant, cost per feature, margin per plan. Laravel's rate limiter also earns its place here as a first line of defence โ€” a per-minute cap per user catches scripted abuse long before the monthly budget does.

Step 4: degrade gracefully at the cap

What happens at the hard limit is a product decision, and "the button stops working" is the worst available option. In rough order of preference:

  1. Route to a cheaper model. Over-quota requests get the small model instead of the flagship. The feature still works; the marginal cost drops by 80โ€“90%. For many features users cannot tell the difference โ€” which tells you something about lever two of cost optimisation generally.
  2. Shrink the work. Shorter context, tighter output caps, summaries instead of full generations.
  3. Defer it. Queue the request for off-peak batch processing at half price: "your report will be ready in the morning" is an acceptable answer for non-interactive features.
  4. Block with a clear path. If you must block, the message names the limit, the reset date, and the upgrade โ€” one click away.

Whichever you choose, tell the user what is happening. Silent degradation reads as "the AI got worse", and that rumour is expensive.

Meter from day one, even if you enforce nothing. Quotas, alerts, pricing tiers and degradation strategies are all one migration away once the usage data exists โ€” and impossible to retrofit honestly once it does not.

The same pattern beyond Laravel

Nothing here is framework-specific: a gate before the call, actual usage recorded after it, counters in something fast, a ledger in something durable. I have implemented the identical shape in Python services with decorators and Celery. The Laravel version just happens to be pleasantly compact, and it slots straight into the AI features I build for SaaS clients.

Adding LLM features to a multi-tenant product and want the cost side under control before launch? This is exactly what I build โ€” get in touch.

#llm cost control saas #laravel #token budgets #metering #multi-tenancy
Keep reading more from the notebook
15 Jun 2026 AI & LLMs Cutting LLM Token Costs in Production: A 2026 Field Guide The five levers I apply to cut LLM API bills in production โ€” prompt caching, model routing, batching, context compaction and output caps โ€” ranked by effort-to-savings. โ†’ 01 May 2026 AI & LLMs Reliable JSON from LLMs: Structured Output Patterns That Hold Up The technique post underpinning every document-AI and integration project: native structured-output/JSON-schema modes vs the tool-call trick, validate-and-retry loops, enum constraints to ki... โ†’ 28 Apr 2026 AI & LLMs Invoice Data Extraction with LLMs: Beyond OCR in 2026 Why vision LLMs beat template-based OCR (reading text vs understanding which number is the total), a production pattern of JSON-schema extraction plus confidence-based human review, and the... โ†’

Planning something like this?

In my work I build exactly the kind of systems this post is about โ€” Laravel, AI, and software that has to hold up in production. Tell me what you're building and I'll tell you honestly how I'd approach it.

Let's talk

Dealing with this yourself?

I have done this end to end. Tell me what is slow, broken, or blocked and I will give you an honest read on it โ€” including if the answer is that you do not need me.

Get in touch โ†— WhatsApp +91 91175 22222 โ†— LinkedIn โ†—