How to attribute AI cost per customer in your SaaS product
A technical guide to building per-customer cost attribution for AI features — and why this is suddenly a board-level conversation in 2026.
TL;DR
If your SaaS product has AI features, your CFO and board are about to ask you what AI costs per customer. Most engineering teams cannot answer this, because their LLM calls weren't instrumented for per-customer attribution from day one.
The fix is a three-layer pattern: tag every LLM call with the customer context, push the data to a queryable store, and build the dashboards that turn raw telemetry into the answer your CFO is asking for.
This post walks through the implementation: what to tag, how to tag it, where to store it, how to build the dashboards. The math is straightforward. The discipline is harder.
The reason this matters more than ever: AI has turned variable cost into a real COGS line for SaaS, and the unit economics conversation is changing fast.
Why this question suddenly matters
For most of SaaS history, the cost of serving an additional customer was roughly zero. Bandwidth, database storage, compute — all so cheap that gross margins stayed comfortably in the 75-85% range. Software was the rare business where you could grow customers without growing variable cost.
AI features broke that.
A customer using your AI summarization feature heavily can cost you $80 a month in API spend. A customer who doesn't use it costs you nothing. The COGS line is suddenly user-dependent, feature-dependent, and growing fast.
What this means in practice:
- The Datadog State of AI Engineering 2026 report found that median LLM tokens per request grew 2.5x in a single year. The cost-per-customer math from 2024 doesn't hold in 2026.
- The $500M Claude bill that hit the news in May 2026 wasn't a hack — it was a real enterprise client whose token consumption exploded without per-user or per-feature attribution. Nobody could see where the spend was going.
- Uber publicly exhausted its 2026 AI budget by April. The COO described AI costs as "harder to justify under current usage patterns" — exactly the conversation you don't want to have without data.
A year ago, "what does AI cost per customer" was a finance curiosity. Today it's the question your CFO will ask before approving next year's AI budget, and the question your board will ask before the next fundraise. If you can't answer it, you can't defend your unit economics.
The good news: it's a solvable engineering problem. The discipline is straightforward; the work is in being consistent.
What "per-customer cost attribution" actually means
Before the implementation, the data model. Every LLM call your application makes should be tagged with enough context to answer four questions:
- Which customer made this call? (customer ID or tenant ID)
- Which user within the customer? (user ID)
- Which feature triggered it? (feature name or workflow ID)
- Which model handled it? (model name and version)
Plus the raw cost data — input tokens, output tokens, model, timestamp.
With these tags on every call, you can answer:
- "What did AI cost us last month, by customer?"
- "Which feature is driving 80% of our AI spend?"
- "Which customers are unprofitable on AI alone?"
- "How does cost-per-customer correlate with revenue tier?"
Without these tags, you only have aggregate usage from your vendor dashboard, and you'll be reverse-engineering attribution by manually correlating timestamps with application logs. I've seen teams spend a week trying to reconstruct this from data that wasn't tagged at write-time. It's painful and unreliable.
Layer 1 — Instrument every LLM call
Wrap your LLM client so every call captures the context. This is the most important step. If you skip it, the rest of this post is theoretical.
Here's the pattern in Python:
async def call_llm(
prompt: str,
customer_id: str,
user_id: str,
feature: str,
model: str = "claude-3-5-sonnet-20241022",
) -> str:
start = time.time()
response = await anthropic_client.messages.create(
model=model,
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
# Capture cost telemetry
metrics.emit("llm_call", {
"customer_id": customer_id,
"user_id": user_id,
"feature": feature,
"model": model,
"tokens_in": response.usage.input_tokens,
"tokens_out": response.usage.output_tokens,
"cost_usd": calculate_cost(model, response.usage),
"duration_ms": (time.time() - start) * 1000,
"timestamp": datetime.utcnow().isoformat(),
})
return response.content[0].textTwo things matter here:
The signature forces the discipline. Making customer_id, user_id, and feature required arguments means every caller has to think about attribution. Optional arguments get skipped in practice.
The cost calculation happens at write time. Don't try to compute cost retroactively from token counts — model prices change, and you'll end up with bad historical data. Capture the cost in dollars at the moment the call happens.
If you're using LangChain, LlamaIndex, or another framework, the pattern is the same — wrap the client call, not the framework's higher-level abstraction, so you don't miss internal LLM calls the framework makes.
What if you're already deployed without this?
The honest answer: backfill is hard. You can correlate Anthropic Console usage data with your application logs by timestamp, but the accuracy degrades fast for shared API keys. Two practical options:
- Add instrumentation today and accept that you have no historical attribution. Your data starts now. Acceptable if you're a young product.
- Issue separate API keys per customer or per feature. Anthropic and OpenAI both support this. You lose some convenience but gain attribution at the vendor-console level. Worth it if you can't change application code quickly.
Layer 2 — Push to a queryable store
The metrics from layer 1 need somewhere to live. Three reasonable options depending on your stack:
Option A — Time-series database (Prometheus, InfluxDB)
Best if you're already on Prometheus + Grafana. Push the cost metrics as labeled gauges:
llm_cost_total = Counter(
"llm_cost_usd_total",
"Total LLM cost in USD",
labelnames=["customer_id", "feature", "model"],
)
llm_cost_total.labels(
customer_id=customer_id,
feature=feature,
model=model,
).inc(cost_usd)You'll query with PromQL. Aggregations like "total cost per customer in the last 30 days" become a one-line query.
Watch out for: High-cardinality labels. If customer_id is unique per customer and you have 50,000 customers, Prometheus will struggle. For large customer bases, consider hashing customer IDs or using a more permissive store like VictoriaMetrics.
Option B — Data warehouse (Snowflake, BigQuery, Postgres)
Best if your finance team already lives in SQL. Push events to a table:
CREATE TABLE llm_calls (
id UUID PRIMARY KEY,
customer_id VARCHAR NOT NULL,
user_id VARCHAR,
feature VARCHAR NOT NULL,
model VARCHAR NOT NULL,
tokens_in INTEGER,
tokens_out INTEGER,
cost_usd NUMERIC(10, 6),
duration_ms INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_llm_calls_customer ON llm_calls (customer_id, created_at);
CREATE INDEX idx_llm_calls_feature ON llm_calls (feature, created_at);Finance can query this directly. They'll love you for it.
Watch out for: Write volume. If your application makes 10K LLM calls a day, each as a single row, you're fine. If you're making 10M, batch writes or use a streaming pipeline (Kinesis → S3 → Snowflake or similar).
Option C — OpenTelemetry to your existing observability vendor
If you're on Datadog, Honeycomb, or New Relic, emit the cost metrics as OTel spans with custom attributes:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("llm_call") as span:
span.set_attribute("customer.id", customer_id)
span.set_attribute("llm.feature", feature)
span.set_attribute("llm.model", model)
span.set_attribute("llm.cost_usd", cost_usd)
span.set_attribute("llm.tokens_in", tokens_in)
span.set_attribute("llm.tokens_out", tokens_out)
# ... make the LLM callYou get attribution alongside your existing application telemetry. Good if your team already lives in Datadog.
Watch out for: Per-span attribute limits. Some vendors cap custom attributes per span; check your plan.
Layer 3 — Build the dashboards finance actually needs
This is the part most engineering teams skip and then regret three months later when the CFO emails asking why she can't see the data.
Four queries cover most of what's asked:
Query 1 — Cost per customer, last 30 days
SELECT
customer_id,
SUM(cost_usd) AS total_cost,
COUNT(*) AS total_calls,
AVG(cost_usd) AS avg_cost_per_call
FROM llm_calls
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY customer_id
ORDER BY total_cost DESC
LIMIT 50;Use this when finance asks "who are our most expensive customers?"
Query 2 — Cost per feature, last 30 days
SELECT
feature,
SUM(cost_usd) AS total_cost,
COUNT(DISTINCT customer_id) AS customer_count
FROM llm_calls
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY feature
ORDER BY total_cost DESC;This is the question that drives product roadmap decisions. If one feature accounts for 70% of AI spend, that's where to focus optimization work.
Query 3 — Cost per customer per feature
SELECT
customer_id,
feature,
SUM(cost_usd) AS cost
FROM llm_calls
WHERE created_at >= NOW() - INTERVAL '30 days'
GROUP BY customer_id, feature
ORDER BY cost DESC;When a heavy customer shows up in Query 1, this tells you what they're using heavily. Often the answer is one feature, used in a way you didn't anticipate.
Query 4 — Cost growth rate per customer
WITH monthly_cost AS (
SELECT
customer_id,
DATE_TRUNC('month', created_at) AS month,
SUM(cost_usd) AS cost
FROM llm_calls
WHERE created_at >= NOW() - INTERVAL '3 months'
GROUP BY customer_id, month
)
SELECT
customer_id,
month,
cost,
LAG(cost) OVER (PARTITION BY customer_id ORDER BY month) AS prev_month_cost,
(cost - LAG(cost) OVER (PARTITION BY customer_id ORDER BY month))
/ NULLIF(LAG(cost) OVER (PARTITION BY customer_id ORDER BY month), 0) AS growth_rate
FROM monthly_cost
ORDER BY customer_id, month;This catches customers whose AI usage is climbing fast. Often the first signal that a customer is about to become unprofitable on AI alone — or that you should be charging them more.
What this gets you in the budget conversation
The four queries above turn your AI costs from "an unexplained line item growing 30% per quarter" into a managed budget conversation.
Three specific conversations become possible:
The COGS conversation. AI is now a cost-of-goods-sold line item, not an R&D line item. Per-customer cost attribution lets you calculate true gross margin per customer, not assumed gross margin. Finance teams have been asking for this since AI features started shipping; most engineering teams haven't been able to provide it.
The pricing conversation. When you can see that your top 5% of users consume 60% of your AI budget, you can have a real conversation about usage-based pricing — backed by data, not by intuition. Without this data, pricing changes feel arbitrary to both sales and customers.
The optimization conversation. When you know that one feature accounts for 70% of AI spend, you can have a focused conversation about where prompt caching, model routing, or feature redesign would have the highest ROI. Without per-feature attribution, optimization work is guessing.
The pattern across all three: the same data turns reactive budget panic into proactive unit economics management. That's the actual prize. The dashboard is just the mechanism.
A word on AIWatcher
The pattern in this post — instrument, store, query, dashboard — works whether you build it yourself or use a tool. If you're building it yourself, the post above is the playbook.
If you'd rather skip the build and the maintenance, AIWatcher is the operational layer that handles this for you. One-line SDK per LLM call, automatic per-customer and per-feature attribution, dashboards built for both your engineering team and your finance team. Same dashboard also covers your internal AI tool usage (Claude Code, Cursor, Codex CLI on developer machines) if you want both surfaces in one view.
AIWatcher is in design partner mode in 2026 with mid-market AI-native SaaS companies. Request access →
The honest read: most teams will start with the DIY pattern in this post, and most teams will eventually want to replace it. The build is one engineer-week of focused work; the maintenance is a quarter-percent of an engineering team forever. At some point that math stops being worth it.
Closing
The deeper truth: AI has changed software's economics. Per-customer attribution isn't a nice-to-have anymore — it's the difference between knowing your unit economics and guessing at them.
The good news is that the engineering work is straightforward. Tag every call. Push to a queryable store. Build the four dashboards. Most teams can do this in a week.
The harder part is the discipline to do it consistently. Every new LLM call in your codebase needs the tags. Every new feature needs to think about attribution from day one. The teams that get this right treat it the same way they treat logging: not optional, not optimized later, just part of how the code is written.
The CFO conversation gets a lot easier when you have the data.
Danny Lo is the founder of AIWatcher, the operational layer for AI agents. We build per-customer attribution into both your internal AI tool usage and your product's AI features.
See AIWatcher in action
The control loop for AI work — catch it before it runs, prove it after.
Get started