DEV Community

Cover image for Your AI app might be losing money on its heaviest users. Here's how to check.
Mayur Agnani
Mayur Agnani

Posted on

Your AI app might be losing money on its heaviest users. Here's how to check.

If you sell an AI app by subscription, your revenue per user is flat and your cost per user is not.

A light user costs you a few cents a month. A heavy one, or a user stuck in an agent loop, can cost more than they pay you. Your OpenAI bill shows tokens. Your payments dashboard shows revenue. Nothing joins the two by user, so you find out from the invoice.

I built FiscaLLM to answer one question: which users cost me more than they pay? Here's the idea and how to try it in about ten minutes.

What you need

  1. A backend between your app and the AI provider (so apps that call OpenAI straight from the phone aren't covered yet).
  2. A way to know what each user pays: RevenueCat, Stripe (beta), or your own code.

1. Report usage after each AI call

import { createMeter } from "fiscallm"; // npm install fiscallm  (Python: pip install fiscallm)

const meter = createMeter({ apiKey: process.env.FISCALLM_KEY, waitUntil });

const res = await openai.chat.completions.create({ model, messages });
meter.trackOpenAI(res, { userId, feature: "chat" });
Enter fullscreen mode Exit fullscreen mode

The SDK reads only the model name and token counts. It never sees prompts or answers. Events are batched in the background, so it doesn't add latency to your request, and if FiscaLLM is down your app is unaffected.

2. Connect revenue

Paste a RevenueCat or Stripe webhook URL, or send payments yourself with the revenue API. FiscaLLM joins revenue and cost on the user ID.

3. Look at margin per user

You get margin per user, per plan and per free trial, plus which model or feature drives the bill. Then you can act:

  • Budget alerts by email, Slack or webhook when a user goes over.
  • A runaway-loop alert for users whose usage suddenly explodes.
  • Optional caps: mark a budget as Block and call meter.check(userId) before an AI call. It fails open, so an outage never blocks your users.

A few details that mattered

  • No double counting. Events carry client-generated UUIDs, so retries are safe.
  • Cached tokens are priced separately from normal input tokens.
  • Unknown models are flagged as unpriced, not silently counted as zero.
  • RevenueCat aliases are resolved, so a user's anonymous and logged-in IDs merge.

Honest limits

  • Stripe support is beta. I've tested it on sample payloads only, and it's USD only.
  • Caps use cost already recorded, so a user can go slightly over before being refused.
  • Built-in prices are checked against provider pages (ACCURACY.md), but I haven't compared them with a real invoice over weeks yet.
  • No customers yet. The demo dashboard uses sample data.

Try it

I'm looking for the first 10 apps to try it. If you ship an AI app with subscriptions, tell me what's missing in the comments or on the roadmap board.

Top comments (2)

Collapse
 
marcusykim profile image
Marcus Kim •

The recorded-cost limit and fail-open check are worth presenting as a soft budget, not a hard spend ceiling. If ten requests pass meter.check(userId) before their usage events arrive, each can be individually allowed while the batch overshoots. I'd test that burst with event delivery paused, then reconcile estimated reservations against actual token costs. The UUID detail also matters on SDK retries: reuse the original event ID rather than generating a new UUID per send. Are reservations or an explicit maximum-overshoot indicator on your roadmap?

Collapse
 
ontomato profile image
ontomato •

From my experience, annual versus monthly will wreck this chart if you don't call it out. Annual pay looks amazing in the payment month and 'expensive' later with flat usage.

Say whether the view is cash in the selected dates or value spread across the covered period, and then use that same period for AI cost. Payment event is not coverage dates. Keep both and the two views stop fighting.