AI Agent Cost per Customer: Measure Before You Bill
You have an AI agent in production. Twenty customers use it. The Anthropic invoice arrives: €2,400 a month. Do you know what each customer consumes?
Most engineering teams I work with don't. They have the aggregate cost. They don't have cost per customer. That means you're charging the same flat rate to a user sending 200 tokens per session as to one firing multi-step agent chains with 15,000 tokens per operation. You're subsidising your heaviest users with the margin from those who barely touch the system.
Why agent costs aren't flat
An AI agent doesn't consume tokens uniformly. Four factors cause two sessions of the same agent to differ in cost by a factor of 10 or 20:
- System prompt: sent on every call. If you have 2,000 tokens of tool definitions, that's 2,000 input tokens per request, always.
- Conversation history: grows with every turn. A customer on message 20 sends 20 times more accumulated context than a new one.
- Tool results: when the agent queries a CRM, an external API, or a database, the full response is added to the context for the next step.
- Model output: the generated response, which carries the highest price per token.
Without per-tenant measurement you're flying blind. Worse: you're making pricing decisions based on an average that nobody actually pays.
The attribution wrapper: measure without touching your logic
The solution doesn't require a new service or changing how you call the Anthropic API. It's a wrapper around the client that captures usage from every call and attributes it to the active tenant.
import Anthropic from "@anthropic-ai/sdk";
type TenantCost = {
tenantId: string;
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
calls: number;
estimatedUsd: number;
};
// Claude Sonnet pricing per million tokens (update as they change)
const PRICE = { input: 3.0, output: 15.0, cacheRead: 0.3 };
export class TrackedAnthropicClient {
private client = new Anthropic();
private ledger = new Map<string, TenantCost>();
async messages(
tenantId: string,
params: Anthropic.MessageCreateParamsNonStreaming
) {
const response = await this.client.messages.create(params);
const u = response.usage;
const cacheRead = u.cache_read_input_tokens ?? 0;
const billableInput = u.input_tokens - cacheRead;
const costUsd =
(billableInput / 1_000_000) * PRICE.input +
(cacheRead / 1_000_000) * PRICE.cacheRead +
(u.output_tokens / 1_000_000) * PRICE.output;
const prev = this.ledger.get(tenantId) ?? {
tenantId,
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
calls: 0,
estimatedUsd: 0,
};
this.ledger.set(tenantId, {
tenantId,
inputTokens: prev.inputTokens + u.input_tokens,
outputTokens: prev.outputTokens + u.output_tokens,
cacheReadTokens: prev.cacheReadTokens + cacheRead,
calls: prev.calls + 1,
estimatedUsd: prev.estimatedUsd + costUsd,
});
return response;
}
getUsage(tenantId: string) {
return this.ledger.get(tenantId);
}
}
The in-memory ledger works for development and single-instance deployments. In production, persist every call.
Persistence: log usage to the database
CREATE TABLE agent_usage (
id BIGSERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
model TEXT NOT NULL,
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
estimated_usd NUMERIC(10,6) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX ON agent_usage (tenant_id, created_at);
With this table, monthly cost per tenant is a single query:
SELECT
tenant_id,
ROUND(SUM(estimated_usd)::numeric, 2) AS total_usd,
SUM(input_tokens + output_tokens) AS total_tokens,
COUNT(*) AS total_calls
FROM agent_usage
WHERE created_at >= date_trunc('month', NOW())
GROUP BY tenant_id
ORDER BY total_usd DESC;
After two weeks in production, this query already tells you who's profitable and who's not.
Quotas: stop the spend before it explodes
Having the data is the first step. The second is acting before a customer blows past their limit. Add a spend check at the start of every call:
async checkAndTrack(tenantId: string, params: Anthropic.MessageCreateParamsNonStreaming) {
const result = await db.query<{ total: string }>(
`SELECT COALESCE(SUM(estimated_usd), 0) AS total
FROM agent_usage
WHERE tenant_id = $1
AND created_at >= date_trunc('month', NOW())`,
[tenantId]
);
const spent = parseFloat(result.rows[0].total);
const limit = await getTenantSpendLimit(tenantId); // from your plans table
if (spent >= limit) {
throw new Error(
`QUOTA_EXCEEDED: ${spent.toFixed(2)} / ${limit} USD this month`
);
}
return this.messages(tenantId, params);
}
Limits can be static per plan (Free: $5, Pro: $50, Enterprise: custom) or dynamic based on what the customer paid that month. Store them in the same tenants table you already have.
From data to pricing model
Once you have two or three weeks of real data, three very clear groups tend to emerge:
- Heavy users (20%): consume 60-70% of total API cost. On flat-rate pricing, you're losing money on them every month without knowing it.
- Core users (60%): consistent, predictable usage. Your real margin.
- Light users (20%): barely touch the system. They unknowingly subsidise the heavy users.
With this distribution you can design prices based on real usage instead of guesswork. And you can approach heavy users with an honest conversation: here's your actual consumption, your current plan doesn't cover it — instead of discovering the problem on the end-of-month invoice.
Every agent project we build at DAILYMP includes this measurement layer from the first sprint. Not as an add-on: as part of the base architecture. An agent where you don't know the cost per customer isn't a product — it's an experiment that scales in the wrong direction.
Got an agent in production and no visibility into what each customer costs? Let's talk on WhatsApp and review your architecture together.