Token Budgets, Cost Tracking & Chargeback

9 min read Module 6 of 10 Lesson 18 of 28

What you'll learn

  • Set and enforce a hard max_budget on a LiteLLM virtual key, and explain what happens once it's reached
  • Compute real per-request cost from token usage and a provider pricing table, accounting for cached-token discounts from Lesson 17
  • Write a chargeback aggregation that rolls up per-request cost logs into a monthly per-team total
  • Explain why cost tracking has to happen at egress (Lesson 4), after the real token usage is known, not at ingress
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Lesson 1 opened this course with a concrete failure: “nobody can answer how much the marketing team spent on Claude last month without grepping logs across a dozen repos.” This lesson closes that loop for real: enforcing hard budgets so spend never silently blows past a cap, computing real per-request cost from actual usage, and rolling that up into the report that actually answers the question.

Enforcing a Hard Budget

Lesson 7 introduced max_budget when generating a virtual key. The enforcement side matters as much as the setting:

curl -X POST http://localhost:4000/key/generate \
  -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{
    "models": ["production-chat"],
    "max_budget": 50,
    "budget_duration": "30d",
    "metadata": {"team": "fraud-detection"}
  }'
def check_budget_before_call(key_id: str, estimated_cost: float) -> bool:
    spend_so_far = get_current_spend(key_id)   # tracked by the gateway per key
    budget = get_key_budget(key_id)
    # Reject before the provider call, not after: the whole point of a
    # hard budget is that spend never actually exceeds the cap, rather
    # than being reported as having exceeded it after the fact.
    if spend_so_far + estimated_cost > budget:
        raise PermissionError(f"key {key_id} would exceed its ${budget} budget")
    return True

This is why the budget check sits at lifecycle stage 3, before routing and before any provider is contacted (Lesson 4), rejecting an over-budget request there costs nothing; rejecting it after the provider has already answered means the money is already spent regardless of what the gateway does next.

Computing Real Cost

Cost can only be computed accurately after the provider responds with real token usage, which is why it belongs at egress, and why Lesson 17’s cached-token discount has to be accounted for explicitly rather than treating every input token as equally priced:

# Simplified pricing table: $ per million tokens. Real gateways (LiteLLM
# ships one) maintain this per exact model string, updated as providers
# change pricing.
PRICING = {
    "claude-sonnet-4-5": {"input": 3.00, "output": 15.00, "cached_input": 0.30},
    "gpt-4o":             {"input": 2.50, "output": 10.00, "cached_input": 1.25},
    "hosted_vllm/meta-llama/Llama-3.1-8B-Instruct": {"input": 0.00, "output": 0.00, "cached_input": 0.00},
}

def compute_cost(model: str, prompt_tokens: int, completion_tokens: int, cached_tokens: int = 0) -> float:
    rates = PRICING[model]
    billable_prompt_tokens = prompt_tokens - cached_tokens
    cost = (
        billable_prompt_tokens / 1_000_000 * rates["input"]
        + cached_tokens / 1_000_000 * rates["cached_input"]
        + completion_tokens / 1_000_000 * rates["output"]
    )
    return round(cost, 6)

# Egress-stage call, after the real provider response is in hand
cost = compute_cost(
    model="claude-sonnet-4-5",
    prompt_tokens=4200,
    completion_tokens=180,
    cached_tokens=3800,   # from response.usage.cache_read_input_tokens, Lesson 17
)

The self-hosted vLLM deployment’s $0.00 rate above is deliberate: its real cost is the fixed GPU spend from Lesson 8, not a per-token provider bill, so it shows as free in this per-request accounting even though it isn’t actually free to the business, a nuance worth a comment in any real pricing table so nobody misreads it as “self-hosting is literally costless.”

Logging for Chargeback

Every request’s cost needs to land in a log tagged with the virtual key’s team metadata, exactly the metadata set at /key/generate time in Lesson 7:

def log_request_cost(key_metadata: dict, model: str, cost: float, timestamp: str):
    cost_log_entry = {
        "team": key_metadata["team"],
        "model": model,
        "cost_usd": cost,
        "timestamp": timestamp,
    }
    write_to_cost_log(cost_log_entry)  # append to a table/log store, e.g. Postgres, ClickHouse

The Chargeback Report

-- Monthly per-team spend: the concrete answer to Lesson 1's original problem
SELECT
    team,
    model,
    SUM(cost_usd) AS total_cost,
    COUNT(*) AS request_count
FROM gateway_cost_log
WHERE timestamp >= date_trunc('month', now())
GROUP BY team, model
ORDER BY total_cost DESC;
flowchart LR
    Req["Request via virtual key\n(team: fraud-detection)"] --> Egress["Egress: compute real cost\nfrom usage + pricing table"]
    Egress --> Log["Cost log entry\n{team, model, cost, timestamp}"]
    Log --> Report["Monthly chargeback report\nGROUP BY team"]

    style Req fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Egress fill:#4338CA,stroke:#4338CA,color:#ffffff,rx:8px,ry:8px
    style Log fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px
    style Report fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px

This single query, grouping a cost log by the team metadata every virtual key already carries, is the entire mechanism behind the “which team spent what” answer that was architecturally impossible with direct SDK calls in Lesson 1. It costs nothing extra to maintain once budgets and cost logging are in place, it is a byproduct of enforcing budgets correctly. Module 7 builds the remaining governance layer on top of this same virtual-key foundation: role-based access, PII redaction, and audit logging.

Knowledge Check

3 questions to test your understanding

1 Why must real per-request cost be computed at egress (after the provider responds), rather than estimated at ingress (before the call is made)?

2 A virtual key has max_budget set to $50 and has already spent $49.80. What happens when a request arrives that would cost an estimated $0.60?

3 Why does a chargeback report need to roll up individual request-level cost logs rather than relying on each provider's own billing dashboard?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan