Skip to main content
Hat Boy Software

Unit economics for an AI platform, without guessing

How to build a per-customer cost view for a multi-tenant AI platform: pick units, capture cost at the provider, fix attribution, split shared cost.

In one month, Baseline’s AI provider billed about $86. Yet the platform’s own telemetry had logged roughly $1.70 all-time in development, and nothing at all in staging or production. About 100% of real model spend was going unrecorded, and about 73% of the cloud bill could not be grouped by product. For a platform whose pricing depends on knowing what each customer costs to serve, that is not a reporting gap. It means the price is a guess. This post is the method we used to replace the guess with a per-customer unit-economics view.

Baseline is a market-intelligence platform on Google Cloud, with Gemini models on Vertex AI and a BigQuery lakehouse. Its economics have one property that shapes everything below: the cost of tracking a company, person or topic is shared by every customer who watches it. News about a company is fetched and processed once, into a shared corpus, however many customers follow it. So the cost view has to trace spend through monitored subjects to customers, not just tag requests with a tenant. The spend caps built on top of this are in an earlier post.

Choose units that match what drives cost

Ask what makes the bill move, then pick units a customer recognizes. On Baseline, three terms do the work:

  • Subject: one monitored entity, such as a company, person, product or topic. It carries a resolved identity that keys it to the shared corpus. Keeping it fresh is what costs money.
  • Board: a named set of subjects that a customer opens, typically a company with its competitors, products and topics.
  • User: a person who signs in and reads boards.

Cost enters through a short chain of multipliers: an account has boards, boards have subjects, and each subject triggers data-provider requests on every refresh. The number of subjects and the refresh cadence move the bill. A user mostly reads rows that already exist, so cost per user swings whenever someone signs in, while the workload stays the same.

Per request is the wrong unit too. Nobody buys requests, and the request count is an output of subjects times cadence, not an input. Model it as a multiplier chain, then check the model against the provider’s own meter. When the predicted and measured counts agree, the per-unit figures can be treated as measurements rather than estimates.

Capture cost where the provider is called

On Baseline, the $86 against $1.70 gap came from where usage was recorded: above the provider layer, so any call that took another route produced no usage row, no cost and no tenant. We moved recording to the provider layer, so a call path that forgets to record now fails loudly instead of logging nothing.

The pattern works on any platform:

  • Record in the provider base class. Give it a public method that makes the call and records it, around a private abstract method that subclasses implement. A provider that skips the abstract method fails when it is created. Wrappers such as a fallback chain or a cache opt out, so one API call is not counted twice.
  • Catch direct calls. Code that builds its own model client bypasses the base class. A test that walks the source tree and fails when a direct model call has no matching record call keeps new ones from slipping in. Compare counts; a test that only looks for the helper’s name is satisfied by an import line.

The provider’s response has more in it than input and output tokens, and each extra field is a way to miscount:

  • Reasoning tokens. Google’s documentation says you are billed for tokens generated during thinking, and reports them in a separate thoughts_token_count field. Check whether your SDK’s output count includes them; if it doesn’t, reading only that field prices part of the output as free.
  • Provider cache hits. A semantic cache hit in your own code means no API call. A provider context-cache hit means the call happened and the cached input billed at a discount. Recording both as “cached, zero cost” understates exactly the traffic caching is supposed to make cheaper.
  • Fields nobody knows about yet. Compare your token sum with the provider’s reported total and warn on a shortfall, so the next uncounted category shows up the first time it appears.

Fix attribution at the source

With every call recorded, the next question is whether each record names the right tenant. On Baseline, two live defects were billing spend to the wrong tenant or to no one; both now use explicit tenant scoping. Two habits prevent the common versions of this bug:

  • Carry scope on the request. Context variables that hold the tenant don’t cross a process boundary, and queue workers and batch jobs are often where most of the spend is. Pass tenant and board IDs explicitly with the request.
  • Clear scope at the start of every task. Workers that run tasks one after another can hand one task’s tenant to the next. A wrong tenant is worse than an empty one, because it looks plausible.

Work that serves everyone, such as processing a shared corpus, has no tenant by design. Record the subject instead, and allocate the subject’s cost to tenants later. Then make sure the tenant column reaches the warehouse you compute margin in, and alert when a sync stalls, so a stopped table doesn’t look like a quiet one.

Attribution chain: a model call, from any code path, goes through the provider base class, which records input, output, reasoning and discounted cache-read tokens. The usage record carries tenant, board, subject and operation, passed explicitly on the request, with task scope cleared before every task; shared-corpus work records the subject only. Records are mirrored to BigQuery with the tenant column and combined with the detailed billing export, labelled by Kubernetes namespace and split into fixed and variable lines, into a unit view of cost per subject, board and tenant. A daily reconciliation compares recorded cost with the provider bill. Record where the provider is called, carry the scope on the request, and reconcile against the bill.

Allocate shared cost on purpose

When several customers watch the same subject, its cost has to be split, and every way of splitting it is a choice. The common options:

  • Owner pays. Whoever first caused the subject to be tracked carries its recurring cost, and followers read rows that are already paid for. This matches marginal cost: following a subject someone else already tracks adds almost nothing.
  • Equal split. Divide the subject’s cost among every board that watches it. Simple and fair-looking, but each tenant’s cost changes when an unrelated customer adds or removes a board.
  • Weighted by depth. The same subject can be pulled deeply as a board’s main subject and lightly as a competitor on another board. If duplicate pulls collapse into one, the deepest request sets the cost, and weighting by requested depth charges the board that set it.

Illustrative shared-cost split for one subject costing 90 units a month, watched by three boards: A as its main subject at depth 3, and B and C as a competitor at depth 1. Owner pays gives A 90, B 0 and C 0. Equal split gives 30 each. Weighted by depth gives A 54, B 18 and C 18. Numbers are illustrative, not from any client. Same subject, same bill, three defensible answers. Pick one and write down why.

Whichever you choose, deduplicate on resolved identity, not on the name a customer typed; boards name the same company differently.

Separate fixed cost from per-unit cost

Not every line in the bill scales with customers, and fixed cost spread across units hides what a unit really costs. Sort each line by behavior before dividing it.

Baseline’s lakehouse showed why. Iceberg tables in BigQuery are managed tables: Google runs background compaction, clustering and garbage collection, billed as compute. One lakehouse tier billed per active table, in environments that had never had a tenant. Stopping writes there removed about 75% of that bill with no loss of function.

Cloud compute needs the same treatment. On a cluster shared by several products, a product label on a node pool is wrong, because no node pool belongs to one product. Namespaces are the honest boundary. With GKE cost allocation enabled, Google’s documentation says the detailed billing export carries labels such as k8s-namespace and k8s-workload-name. Three details from those docs matter for planning:

  • The data appears only in the detailed usage cost export to BigQuery, and it is not backfilled, so turn it on early.
  • Allocation is based on resource requests, not consumption, so pods with inflated requests look expensive.
  • Capacity nobody requested shows up as kube:unallocated, and node overhead as kube:system-overhead. Treat those as fixed platform cost, not as a product’s.

What the view makes possible

With cost recorded at the provider, carried to the right tenant and subject, and split into fixed and variable lines, per-tenant margin becomes computable and pricing can start from measured cost. The same view shows which boards duplicate work and where capacity runs out.

Keep the cost basis next to the code that enforces plan limits, with a test that fails if they drift.

Checklist

  • Pick units from what drives cost, and model the multiplier chain from customer to provider request.
  • Record usage in the provider base class, so an unrecorded path fails loudly.
  • Add a test that every direct model call has a matching record call.
  • Count reasoning tokens and discounted cache reads, and warn when your token sum falls short of the provider’s total.
  • Pass tenant and board IDs on the request; clear task scope before every task.
  • Record the subject on shared work, and allocate it with a written rule.
  • Carry the tenant column into the warehouse, and alert on silent sync stalls.
  • Reconcile recorded cost against the provider bill and the billing export.
  • Classify every cloud line as fixed or variable, and turn on GKE cost allocation early.

How we can help

We build cost attribution into AI platforms: provider-layer metering, tenant scoping, and unit views that reconcile with the bill. See our Applied AI Engineering service, or get in touch if you can’t yet say what one customer costs to serve.

Share this article

Related articles

Applied AI Engineering

Cutting an agent platform's LLM spend by 79%

How a multi-agent platform's metered model spend fell from about $107 to $22 a day: attribution, three cache layers, model routing and loop caps.

Applied AI Engineering

Adding an AI chat assistant to a static site

How we added an AI chat assistant to a static Hugo site: no new service, no CORS preflight, idle-based lead capture, hard limits and a kill switch.

← All articles

Not sure where to start? Start with an assessment.

A senior review of your app, cloud estate, or AI platform. Scoped and quoted before work starts. It ends in a prioritized plan, so you decide what to fix and when.

Talk to an engineer