TingStudio
Home / Blog / This post
Development

Usage-based billing for AI SaaS: what the Stripe docs don't tell you

When I shipped my first AI product, billing was the one thing I was sure would be easy. Stripe has a pricing page, meters, webhooks — how hard could "charge people for tokens" be?

Three weeks later I had a spreadsheet full of 8-decimal numbers, a test invoice that didn't match my own logs by $0.03, and a genuine fear that one of my $19 customers was going to wake up with a $400 OpenAI bill in my name.

Here's what I learned the hard way, plus the small set of patterns that eventually made it work. If you're selling an AI wrapper, agent, or API on top of OpenAI / Claude / Gemini / DeepSeek, this is the stuff the Stripe Metered Billing docs assume you already know.

The three pricing models (and why two of them lose money)

Your first instinct is probably flat-rate: "$19/month, unlimited." It sells, it's simple, and it is a trap. LLM costs are wildly skewed — a handful of power users running agent loops consume 100x what a normal user does. With unlimited flat pricing, your best "customers" are your biggest cost center.

The opposite instinct is pure pay-per-token. It's perfectly fair on paper, and nobody buys it. The moment users can see the meter ticking, usage drops. Nobody wants to build on an API where every typo in a prompt costs money. Pure metered = bill shock = churn after every heavy week.

The model that actually works for AI products is hybrid: a fixed monthly fee that includes a quota, plus metered overage above it. This is how Cursor, GitHub Copilot, and the OpenAI/Anthropic team plans all work. Predictable baseline for the customer, no unlimited giveaway for you.

The math nobody tells you to do: blended cost

Before you set a single price, you need one number: your blended cost per 1M tokens.

Input and output tokens cost different amounts (output is usually 3–8x more expensive). At a typical 4:1 input:output mix:

blended_cost_per_M = 0.8 * input_price + 0.2 * output_price

Run that on real 2026 prices and you see why there is no single safe price for "an AI product":

  • Budget route (Flash-Lite / Luna tier, ~$0.20 / $1.20): ~$0.40 / 1M
  • Smart mix of cheap + mid models: ~$1.00 / 1M
  • Claude Sonnet-tier routing (~$2 / $10): ~$3.60 / 1M
  • Flagship Opus-tier routing ($5+ / $25+): ~$9.00+ / 1M

That's a 22x spread. A $19 plan can safely include ~14M tokens on the budget route — and only ~0.6M on the flagship route. Price as if everyone uses the cheap model while users route everything to the flagship, and you'll know exactly how it feels to lose money on every single sale.

The code shape that worked

After burning a week on it, I converged on three pieces. All of this is plain Python with zero third-party dependencies for the core logic — the Stripe SDK only touches the edge.

1. Record usage server-side, never trust the client. Token counts come from the provider's own usage object. The parser handles the OpenAI, Anthropic, and Gemini response shapes (they're all slightly different — prompt_tokens vs input_tokens vs promptTokenCount, and Anthropic's cache tokens are reported separately):

from billing_module import record_usage_from_response, BillingEngine

usage = record_usage_from_response(response)   # success=False on any parse error
engine = BillingEngine()
breakdown = engine.bill("cus_123", usage, model_key="sonnet-premium")
# Failed calls bill zero by default (bill_on_failure=False)

2. Validate the plan economics before you publish the price. I turned the "am I about to lose money?" spreadsheet into one function. It checks that the full included allowance fits inside your fee after payment fees with margin left, and that the overage price is at least 1.6x backend cost:

from billing_module import validate_plan_economics, PLAN_CATALOG

r = validate_plan_economics(PLAN_CATALOG["budget-pro"], blended_cost_per_m=0.40)
print(r["status"])                          # SAFE
print(r["max_safe_included_tokens"])        # how much you can actually include
print(r["break_even_overage_price"])        # the floor for your overage price

Point it at a bad plan — say, cheap fee, flagship cost — and it comes back UNSAFE instead of quietly letting you sell a loss leader. It returns SAFE / WARNING / UNSAFE, and the WARNING state catches things like "payment + platform fees exceed 10% of revenue."

3. Make quota weighted, not raw tokens. This is the single biggest anti-abuse lever, and it's the same trick Cursor uses with "premium requests." Every model gets a quota_weight equal to its cost ratio vs the cheapest route (1.0 → 2.5 → 9.0 → 22.5):

# inside the tracker: units = tokens * quota_weight / 1_000_000
units = tracker.weighted_units(total_tokens, model_key="opus-flagship")

Premium models drain the same included quota 9–22x faster. On a 10M-unit plan: 5M Luna tokens uses half the quota; 1M Sonnet-tier tokens nearly wipes it; 5M Opus-tier tokens blows past it on day one. Normal users on cheap models feel zero friction; the guy who tries to run Opus all day on a $19 plan self-limits.

Five things that bit me (so they don't have to bite you)

  1. Report only overage to Stripe — never total usage. The base fee already covers the included quota. If you report every token to your metered price, every included token gets double-billed. I named the wrapper report_overage() specifically so the code can't do the wrong thing by accident.
  2. Floats will quietly steal your money. A per-token price like $0.28/1M is 0.00000028 per token. Round each call to cents and it rounds to $0.00 — a revenue leak that's invisible until month two. Do all internal math with Decimal (8+ places), use Stripe's 12-decimal unit_amount_decimal, and round to cents only on the final invoice. per_token_price_str(0.28) exists so I never hand-type that string.
  3. Idempotency keys are not optional. Network blips cause retries; retries without an idempotency key cause double-charged meter events. Every MeterEvent needs a stable UUID identifier, and your webhook handler must de-duplicate (handle_webhook verifies the signature on the raw body and drops events it's already seen). Bonus failure mode: Stripe outages. Events go to a local fallback queue and flush later — usage never gets dropped on the floor.
  4. No caps = one buggy loop away from disaster. Per-customer token caps and dollar caps, checked before the LLM call, with graded alerts at 80/90/100%. Overage is opt-in and has a hard USD ceiling (overage_max_usd, default $20) — no account, human or runaway script, can produce an unlimited bill. I also enforce a daily sub-cap (~10% of monthly quota) so nobody burns the whole month on day 1 and then disputes the charge, plus an anomaly check that auto-throttles RPM to 50% when a day spikes 5x above the customer's trailing 7-day baseline.
  5. Failed calls must bill zero. Timeouts, 429s, truncated streams — if the user didn't get the output, they don't pay. Record the failure in the audit log anyway (failures cost you LLM money and reveal reliability problems), but mark it billed: false. That one rule prevents a whole category of refunds and bad reviews.

The thread connecting all five: billing is append-only events plus an audit log you can actually read. One JSON line per call with the same idempotency key you sent Stripe means when someone asks "what did you bill me for?", you have the answer instead of a shrug.

Wrapping up

Stripe's metered infrastructure is genuinely good — meters, invoices, dunning, tax are all solved. What it doesn't give you is the layer above it: blended-cost math, never-lose-money plan checks, weighted quota, caps, anomaly throttling, and the 10 or so edge cases that turn a billing system into a support-ticket generator.

🧾 Ship token billing this week, not in three

I packaged everything — the pure-Python billing engine, Stripe glue with retries/queue/webhooks, a runnable quickstart, and 7 docs including a button-by-button Stripe walkthrough and a verified 2026 model pricing table — as the AI SaaS Token Billing Kit. The demo runs with no Stripe account and no API keys.

Whether or not the kit is useful to you, steal the patterns: hybrid plans, blended-cost validation, weighted quota, Decimal everywhere. Your margins will thank you.

← Back to all posts