← Back to Blog
AI AgentsMulti-TenantSaaSCost ControlBudgetLLMTokenFenceArchitecture

Multi-Tenant AI Cost Control: How to Budget AI Agents Per Customer in SaaS Apps

·12 min read

The Multi-Tenant AI Cost Problem Nobody Talks About

You've built AI features into your SaaS app. Customers love it. Then one customer discovers they can ask your AI agent to "analyze everything" and your monthly API bill jumps from $500 to $8,000 overnight.

This is the multi-tenant AI cost problem: when multiple customers share your AI infrastructure, one power user can consume the budget meant for everyone else. Traditional rate limiting doesn't cut it — you need per-customer cost budgets that track actual dollar spend, not just request counts.

This guide shows you how to implement per-customer AI cost controls in a multi-tenant SaaS app, from basic per-tenant budgets to production-grade tier-based systems.

Why Request Rate Limiting Isn't Enough

Most SaaS teams start with request rate limiting: "each customer gets 100 AI requests per day." The problem? Not all requests cost the same.

  • A simple classification call costs ~$0.001
  • A research agent workflow costs ~$2.00
  • A document analysis with 50K tokens costs ~$1.50
  • A multi-step agent chain costs $3-10

A customer making 50 classification calls costs you $0.05. A customer making 50 research queries costs you $100. Rate limiting treats them the same. Dollar-based budgeting doesn't.

Architecture: Per-Tenant Cost Isolation

The key insight: every AI API call in your SaaS app should be tagged with a tenant ID and tracked against that tenant's budget. Here's the architecture:

from tokenfence import guard

class TenantAIService:
    """Per-tenant AI cost isolation for multi-tenant SaaS."""
    
    def __init__(self, base_client):
        self.base_client = base_client
        self.tenant_guards = {}
    
    def get_guard(self, tenant_id: str, tier: str = "free"):
        """Get or create a cost-guarded client for a specific tenant."""
        if tenant_id not in self.tenant_guards:
            budget = self._get_tier_budget(tier)
            self.tenant_guards[tenant_id] = guard(
                self.base_client,
                max_cost=budget["daily_limit"],
                max_requests=budget["max_requests"],
                model_downgrade=budget.get("downgrade_chain"),
            )
        return self.tenant_guards[tenant_id]
    
    def _get_tier_budget(self, tier: str) -> dict:
        """Define per-tenant budgets by pricing tier."""
        tiers = {
            "free":       {"daily_limit": 0.50,  "max_requests": 50,   "downgrade_chain": {"gpt-4o": "gpt-4o-mini"}},
            "starter":    {"daily_limit": 5.00,  "max_requests": 500,  "downgrade_chain": {"gpt-4o": "gpt-4o-mini"}},
            "pro":        {"daily_limit": 25.00, "max_requests": 2000, "downgrade_chain": None},
            "enterprise": {"daily_limit": 100.00,"max_requests": 10000,"downgrade_chain": None},
        }
        return tiers.get(tier, tiers["free"])
    
    def call(self, tenant_id: str, tier: str, messages: list, model: str = "gpt-4o"):
        """Make an AI call scoped to a specific tenant's budget."""
        client = self.get_guard(tenant_id, tier)
        return client.chat.completions.create(
            model=model,
            messages=messages,
        )

Every tenant gets their own guarded client. When their budget runs out, their requests fail gracefully — but other tenants are unaffected.

Tier-Based Budget Design

Your AI cost tiers should map directly to your SaaS pricing tiers. Here's a reference design:

TierMonthly PriceAI Budget/DayAI Budget/MonthMax Requests/DayModel Access
Free$0$0.50$1550GPT-4o-mini only
Starter$29/mo$5.00$150500GPT-4o (auto-downgrade)
Pro$99/mo$25.00$7502,000GPT-4o + Claude Sonnet
Enterprise$499/mo$100.00$3,00010,000All models, no downgrade

The margin math: If your Pro tier costs $99/mo and the AI budget is $750/mo, you're underwater. In practice, most Pro users use 20-40% of their budget. The key is monitoring actual usage distribution and adjusting budgets so your 90th percentile user stays profitable.

Implementation: Budget Reset and Rollover

Daily budgets need to reset. Some customers want rollover. Here's how to handle both:

from datetime import datetime, timedelta
from tokenfence import guard

class BudgetManager:
    def __init__(self, db):
        self.db = db
    
    def get_remaining_budget(self, tenant_id: str) -> float:
        """Calculate remaining budget for today."""
        usage = self.db.get_today_usage(tenant_id)
        tier = self.db.get_tenant_tier(tenant_id)
        daily_limit = TIER_BUDGETS[tier]["daily_limit"]
        
        # Optional: add rollover from unused yesterday
        if TIER_BUDGETS[tier].get("rollover"):
            yesterday_unused = daily_limit - self.db.get_yesterday_usage(tenant_id)
            rollover = min(yesterday_unused, daily_limit * 0.5)  # Cap rollover at 50%
            daily_limit += max(0, rollover)
        
        return max(0, daily_limit - usage)
    
    def create_guarded_client(self, tenant_id: str, base_client):
        """Create a client guarded by remaining daily budget."""
        remaining = self.get_remaining_budget(tenant_id)
        if remaining <= 0:
            raise BudgetExhaustedError(f"Tenant {tenant_id} daily AI budget exhausted")
        
        return guard(base_client, max_cost=remaining)

Handling Budget Exceeded Gracefully

When a tenant hits their budget, don't just return a 500 error. Show them exactly what happened and what they can do about it:

from tokenfence.errors import BudgetExceeded

def handle_ai_request(tenant_id, prompt):
    try:
        client = budget_manager.create_guarded_client(tenant_id, openai_client)
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
        )
        return {"result": response.choices[0].message.content}
    except BudgetExceeded as e:
        return {
            "error": "ai_budget_exceeded",
            "message": "You've reached your daily AI usage limit.",
            "used": e.total_cost,
            "limit": e.max_cost,
            "resets_at": next_midnight_utc().isoformat(),
            "upgrade_url": "/settings/billing",
        }

Critical UX decision: Budget exceeded is an upsell moment, not an error. Show the upgrade path prominently. Many SaaS companies report 15-25% conversion on AI budget upgrade prompts.

Per-Feature Cost Allocation

Not all AI features in your app cost the same. Break your budget into feature-level allocations:

class FeatureBudgetAllocator:
    """Allocate per-tenant budget across features."""
    
    FEATURE_WEIGHTS = {
        "chat": 0.40,          # 40% of budget
        "document_analysis": 0.30,  # 30% heavy per-call cost
        "summarization": 0.15,  # 15% moderate usage
        "classification": 0.10, # 10% cheap per call
        "search": 0.05,        # 5% minimal AI cost
    }
    
    def get_feature_budget(self, tenant_id: str, feature: str) -> float:
        total_remaining = budget_manager.get_remaining_budget(tenant_id)
        weight = self.FEATURE_WEIGHTS.get(feature, 0.05)
        return total_remaining * weight
    
    def create_feature_client(self, tenant_id: str, feature: str, base_client):
        feature_budget = self.get_feature_budget(tenant_id, feature)
        return guard(base_client, max_cost=feature_budget)

Why feature-level budgets? Without them, a customer who runs one expensive document analysis can exhaust their daily budget, blocking cheaper features like search for the rest of the day.

Abuse Prevention: Catching AI Cost Attacks

In multi-tenant apps, some users will try to exploit your AI features. Common attack patterns:

  1. Prompt stuffing — sending maximum-length prompts to maximize token consumption
  2. Batch abuse — automating API calls to exhaust free-tier budget as fast as possible
  3. Context window exploitation — including massive documents to force expensive processing
  4. Account multiplication — creating multiple free accounts to bypass per-tenant limits
from tokenfence import guard, Policy

# Layer 1: Cost budget
client = guard(base_client, max_cost=5.00)

# Layer 2: Policy enforcement
policy = Policy()
policy.deny("tool:database:delete:*")
policy.deny("tool:email:send:*")
policy.require_approval("tool:payment:*")
policy.allow("tool:search:*")

client_with_policy = guard(base_client, max_cost=5.00, policy=policy)

# Layer 3: Input validation
def validate_input(prompt: str, tenant_tier: str):
    max_chars = {"free": 2000, "starter": 5000, "pro": 15000, "enterprise": 50000}
    if len(prompt) > max_chars.get(tenant_tier, 2000):
        raise InputTooLongError("Input exceeds your tier's limit")

Database Schema for Cost Tracking

CREATE TABLE ai_usage_log (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    feature VARCHAR(50) NOT NULL,
    model VARCHAR(50) NOT NULL,
    input_tokens INT NOT NULL,
    output_tokens INT NOT NULL,
    cost_usd DECIMAL(10,6) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE ai_usage_daily (
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    date DATE NOT NULL,
    total_cost DECIMAL(10,4) NOT NULL,
    total_requests INT NOT NULL,
    budget_limit DECIMAL(10,4) NOT NULL,
    PRIMARY KEY (tenant_id, date)
);

CREATE INDEX idx_ai_usage_log_tenant_date 
ON ai_usage_log (tenant_id, created_at DESC);

Integration: Next.js API Routes

// app/api/ai/chat/route.ts
import { guard } from 'tokenfence';
import { getSession } from '@/lib/auth';
import { getTenantBudget } from '@/lib/billing';

export async function POST(req: Request) {
  const session = await getSession();
  const budget = await getTenantBudget(session.tenantId);
  
  const client = guard(openai, {
    maxCost: budget.remaining,
    maxRequests: budget.remainingRequests,
  });
  
  const response = await client.chat.completions.create({
    model: budget.allowedModel,
    messages: await req.json(),
  });
  
  await logUsage(session.tenantId, response.usage);
  return Response.json(response.choices[0].message);
}

The 5-Step Implementation Plan

  1. Week 1: Instrument — Add TokenFence to every AI call. Tag with tenant_id. Start logging costs.
  2. Week 2: Analyze — Review per-tenant cost data. Identify top 10% spenders. Calculate margin per tier.
  3. Week 3: Enforce — Set per-tenant daily budgets based on tier. Add graceful budget-exceeded handling.
  4. Week 4: Optimize — Add auto model downgrade for lower tiers. Implement feature-level budgets. Build usage dashboard.
  5. Week 5: Monetize — Add AI usage upsell prompts. Create AI-specific add-on pricing. Track conversion from budget-exceeded to upgrade.

Start Now

Multi-tenant AI cost control isn't optional — it's the difference between a profitable AI feature and one that bankrupts your SaaS. Start with per-tenant budgets today, then layer on tier-based controls, feature isolation, and usage dashboards.

# npm
npm install tokenfence

# Python
pip install tokenfence

Three lines of code to add per-tenant cost guardrails. Budget isolation, auto model downgrade, kill switch, policy engine — all built in.

TokenFence is open source (MIT). Community edition is free forever. Pro adds multi-tenant dashboard, alerting, and budget pooling. tokenfence.dev/pricing

Ready to protect your AI budget?

Two lines of code. Per-workflow budgets. Automatic model downgrade. Hard kill switch.