---
id: saas/custo-ia-por-cliente
industry: SaaS and technology
usage: specific
effort: high
grain: User x model x day (fact); customer x month (unit economics)
---

# AI cost by customer, user and model

How much each customer, plan and user costs in LLM spend, who consumes far more than they pay and whether price covers usage, with a cost that matches the provider's invoice.

## Executive summary

**For:** SaaS with AI inside the product that pays LLM providers per use and charges customers by subscription or credits.

**What it solves**

- AI cost matches the provider's invoice, day by day.
- Every unit of cost attributed to customer, user and model, or flagged as unattributed.
- Heavy users and plans with negative margin surface on their own.

**Why it matters:** In an AI product, variable cost grows with usage, not with the subscription: a few users can eat a whole plan's margin. The provider bills by key or project, not by customer, and analytics tools estimate cost without the cache discount. Without one monetary source and an allocation rule, pricing is set in the dark.

**Business questions it answers**

- How much did we spend on AI yesterday, by model and by product?
- Which users spent more than US$ 20 in a day?
- What is the average cost per request and how did it change after the model switch?
- What is each customer's AI cost against what they pay, by plan?
- How much of the cost is the team's internal usage?
- Which customers are above the p99 of consumption?

**What you need**

- Access to the providers' or AI gateway's cost and usage APIs
- The user and customer identifier recorded on every call
- Plans and prices from billing, and the declared exchange rate

**What you get**

- A user x model x day consumption table, with tokens, requests and cost
- Cost by customer and plan against revenue, per month
- Alerts for users and customers above the limit
- An AI agent that answers about cost from the right monetary source

## Detailed

### The problem

The provider bills by project or key, the analytics tool knows which user made the call, and the two don't match. Analytics prices every token as new and inflates cost; the cost API doesn't return user and model in the same query; the provider's day is UTC. And shared cost, such as model infrastructure, has no owner. The result is a per-customer cost that doesn't tie to the invoice and a price nobody can defend.

### Business rules

1. **One source for money, another for attribution.** Cost in money comes from the invoice or the cost API of the provider or gateway. Analytics events say who, in which project and with which tool, but don't work as a value: in a real base, analytics sent every token as new, without the prompt cache discount, and inflated cost several times. Use analytics only to attribute, and reconcile both sources by day. (Avoid: Summing the estimated cost that comes in analytics events.)
2. **The day's total has to close.** Before trusting a day, check that the sum of cost by user rebuilds the model's cost for the day (for example, 99% or more, with an absolute tolerance for models with negligible cost) and that the users collected cover the total reported by the API. A day that doesn't close raises an alarm and is collected again. (Avoid: Saving whatever day the API returned, without checking the total.)
3. **User and model need two queries.** Report APIs usually group by model or by user, not both. Query cost by model for the day, as the reference, and, for each model, cost by user. Write with a merge on date, product, model and user, so a day can be collected again without duplicating. (Avoid: Spreading the model's cost across the day's users without querying the detail.)
4. **Shared cost allocation preserves the total.** Cost with no user at the source (infrastructure, batch calls, fixed provider charges) is allocated by a declared usage weight: requests, tokens or events. The allocated sum equals the original cost. Whatever has no traceable usage that day goes to a visible unattributed bucket, never spread silently. (Avoid: Leaving shared cost out of per-customer cost, or spreading the remainder without saying so.)
5. **Canonicalize user and model.** The same user shows up as email, login id and app id. Bring them all to one canonical id and sum the fragments. Model names change with versions and dates: normalize to a canonical model with the provider alongside. Flag development users and check, on every load, the map from API key to product: a swapped key and label raise no error. (Avoid: Grouping by whatever identifier the API returned.)
6. **Internal usage is not cost to serve.** The team's own usage (tests, internal agents, development) is a company expense, not product cost. Put it on its own line and take it out of per-customer cost and plan margin. (Avoid: Adding the team's usage to customers' cost.)
7. **Provider day in UTC, business day in São Paulo.** The provider closes the day in UTC, which covers parts of two São Paulo days. For daily views, declare which day you are using; monthly, the difference is small. Never mix UTC-day and local-day columns in the same sum. (Avoid: Comparing the provider's daily cost with local daily usage.)
8. **Source currency kept alongside.** Most API cost is billed in dollars. Keep the amount and the source currency, convert at the rate of the accrual day in a single layer, and never sum amounts in different currencies. FX exposure = sum of the source amount in USD. (Avoid: Keeping only the converted amount, or summing USD and BRL.)
9. **Customer cost against what they pay.** Sum the cost of the customer's users in the month and compare it with their MRR and the credits they bought. Show the cost distribution per user in percentiles (p50, p75, p90, p99): the average hides heavy users. A simple alert threshold works, such as a user spending more than US$ 20 in a day. (Avoid: Looking only at average cost per user.)
10. **Usage events deduplicated.** Usage events sent by the product can arrive twice. Deduplicate by a hash of the event content before summing tokens, and keep call retries as their own metric: a retry consumes tokens and delivers no answer. (Avoid: Summing tokens from every event received.)

### Which source answers each question

Money on one side, attribution on the other, reconciled by day.

- **how much we spent, in money** → Provider or gateway cost API (`amount.value (OpenAI costs)`)
- **who spent: user, project, tool** → Gateway by user or product events
- **tokens read from cache** → Separate cached from new tokens (`input_cached_tokens (OpenAI usage)`)
- **cost with no user at the source** → Allocation by usage weight, total preserved
- **cost with no traceable usage that day** → Unattributed cost, its own line
- **the team's own usage** → Internal line, outside cost to serve

### Definitions

| Metric | Definition |
|---|---|
| AI cost | amount charged by LLM providers and the gateway for the day, from the monetary source, in the source currency and converted at the day's rate |
| Attributed cost | share of cost tied to a user and a customer, directly or by allocation |
| Unattributed cost | the day's cost with no traceable usage, published on its own line |
| Cost per request | sum of cost / sum of requests, by model and day |
| Customer cost to serve | cost attributed to the customer's users in the month, excluding internal usage |
| Plan AI margin | (plan MRR - cost to serve of the plan's customers) / plan MRR, per month |
| Cache rate | input tokens read from cache / input tokens, by model and day |
| Heavy user | user above the p99 of the product's daily cost, or above the declared limit |

### Pitfalls

- **Analytics inflated the cost.** Cost from analytics events treated every token as new. In one month, the provider recorded most of a model's tokens as cached, and analytics priced all of them as new input. How to avoid: Value always from the cost API; analytics only to attribute.
- **An extraction that lost days without failing.** The analytics events load reported success while picking up only a few minutes of data in about a third of runs, and whole days disappeared with no alert. How to avoid: Reconcile by day with the monetary source and alarm when coverage drops.
- **Swapped key and label.** The product was defined by the API key, and one product's key saved under another's label sent consumption to the wrong product, with no error at all. How to avoid: Validate the key-to-product map on every load.
- **Dollars without saying how many.** The source-currency amount was removed for looking duplicated, and with most API cost in dollars nobody could state the FX exposure. How to avoid: Keep amount and source currency paired.
- **A duplicated event summed tokens.** Usage events arrived repeated through the message queue, and token consumption for some flows showed up doubled. How to avoid: Deduplicate by a hash of the event content.

### Acceptance test

- [ ] For each day and model, the sum of cost by user rebuilds at least 99% of the model cost reported by the provider.
- [ ] Attributed cost + unattributed cost + internal usage = total cost for the day, with no difference.
- [ ] Total cost for the month matches the providers' invoices (difference under 1%, explained by FX).
- [ ] No user appears with two different ids on the same day.
- [ ] Collecting a day again doesn't change the total of other days or duplicate rows.

### Context document

```text
AI cost by customer
- The money value comes from the provider's or gateway's cost API. Analytics events are only for attributing user and project, never as a value.
- Day cost = attributed + unattributed + internal usage.
- Shared cost is allocated by a declared usage weight (<weight>). The allocated sum equals the original.
- The team's internal usage doesn't go into per-customer cost or plan margin.
- The provider's day is UTC. Say which day you are using.
- Amounts in the source currency are never summed across currencies. Conversion at the accrual day's rate.
- Users and models are in canonical ids.
- Per-customer cost is compared with the customer's MRR and credits. Use percentiles, not only the average.
- If a day hasn't passed the completeness lock, say the day hasn't closed yet.
Per-client exceptions:
- <client>: plan <plan> includes <n> AI credits per month.
- <client>: project <project> is internal usage.
```

### Variations

- **Cost per delivery:** When each send or generated document has its own LLM cost record, sum by delivery and compare with the unit price.
- **Impact of each change:** A daily series of cost and savings attributed to each model or prompt change, to prove the change paid off.
- **Bot quality next to cost:** Human interventions and retries per conversation, to know whether the cheaper model is hurting service.
