Guides

How to track OpenAI cost per user

In short: every OpenAI response tells you how many tokens it used. To get cost per user, read that usage, multiply it by the model's price, and add it to a running total keyed by your user ID. Then set it next to what that user pays you.

The OpenAI dashboard shows spend by project, key and model, but not by your users. If you charge a flat subscription, that gap is where margin quietly disappears: one user can cost pennies and another can cost more than they pay.

1. Read the usage object

Every non-streaming Chat Completions response includes a usage block. You only need three numbers from it, and you never need the message text.

const res = await openai.chat.completions.create({ model, messages });

const u = res.usage;
// u.prompt_tokens                          input tokens (includes cached ones)
// u.prompt_tokens_details.cached_tokens    the part of the input served from cache
// u.completion_tokens                      output tokens (includes reasoning tokens)

Streaming: a stream returns no usage unless you ask for it. Pass stream_options: { include_usage: true } and read the final chunk, otherwise your streamed calls cost money and show up as zero.

2. Turn tokens into dollars

Providers publish prices per million tokens, with separate rates for input, cached input and output. The formula:

cost = (prompt_tokens - cached) × input_rate
     +  cached                × cached_rate
     +  completion_tokens     × output_rate        (rates are USD per token = price ÷ 1,000,000)
// Example rates only. Look up the current numbers on the provider's pricing page.
const PRICE_PER_MTOK = { "gpt-5-mini": { input: 0.25, cached: 0.025, output: 2.0 } };

function costUsd(model: string, u: Usage) {
  const p = PRICE_PER_MTOK[model];
  if (!p) return null; // unknown model: flag it, don't count it as $0
  const cached = u.prompt_tokens_details?.cached_tokens ?? 0;
  return ((u.prompt_tokens - cached) * p.input + cached * p.cached + u.completion_tokens * p.output) / 1e6;
}

Three mistakes that make the numbers wrong:

3. Store it per user

Write one row per call (with a unique event ID so a retry can't count twice) and roll it up per user per day. The daily rollup is what you query for dashboards; the raw rows are only for debugging, so you can delete them after a while.

events(id uuid primary key, user_id, ts, model, input_tokens, cached_tokens, output_tokens, cost_usd, feature)
daily_user_cost(user_id, day, cost_usd, requests)   -- updated in the same transaction as the insert

Do this off the request path (a queue, or waitUntil on serverless), so a database hiccup never slows or fails a user's AI call.

4. Compare it with what they pay

Cost alone isn't the answer. A user costing $8 a month is fine on a $30 plan and a loss on a $7.49 plan. You need revenue per user, net of store fees and tax, and the same user ID on both sides. If you sell through the App Store or Google Play, RevenueCat webhooks give you that.

Then margin is simply net revenue − AI cost over the same window. Sort users by it, and the ones losing you money are at the top.

The shortcut: let FiscaLLM do steps 2 to 4

Steps 2 to 4 are the same for everyone, so we built them. One line after each call sends the model and token counts (never your prompts or answers). Prices, cached tokens, de-duplication, per-user rollups, revenue and alerts are handled.

import { createMeter } from "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" });
from fiscallm import Meter
meter = Meter(api_key=os.environ["FISCALLM_KEY"])

res = client.chat.completions.create(model=model, messages=messages)
meter.track_openai(res, user_id=user_id, feature="chat")

It's free and open source. See the demo dashboard (no sign-up), read how it works, or start free.

Questions

Can the OpenAI dashboard show cost per user?

Not by your own users. It groups by project, API key and model. Some teams create one key per user, which doesn't scale; tracking usage in your own code does.

Does tracking slow my app down?

Not if you send events in the background and never await them on the request path.

Do I need to send prompts to track cost?

No. Cost depends only on the model and the token counts. Never send message content.