BackLLM gateway finance audit

See profit as the space between revenue and supplier cost.

Meter input, output, and cached tokens once. Let amount stay as customer revenue, then add analytics-only metrics for total revenue and cost so finance can chart margin without double charging customers.

revenue
$20,350
total_tokens.amount
cost
$5,899
cost.amount
gross margin
71.0%
computed after query
POST /analysis/query ยท grouped series

Revenue over cost, profit in between

$14,451 gross profit
Revenue
Cost
Gross profit band
Metric abstraction

No finance-only fields. Just rated metrics.

The customer-facing token metrics keep their quantity and amount. Analytics-only metrics reuse those results for reporting, without entering debit.

metricquantityamountdebit
input_tokens$.usage.input_tokensCustomer revenueyes
output_tokens$.usage.output_tokensCustomer revenueyes
cached_tokens$.usage.input_tokens_details.cached_tokensCustomer revenueyes
total_tokensitems[input/output/cached]Revenue rollupanalytics_only
cost0Supplier price bookanalytics_only
Rule

Define metrics once

Token metrics carry customer amount. Virtual metrics reuse those facts for revenue rollup and supplier cost.

Event

Report price hints with the request

The gateway sends customer prices and cost prices in the same event, keyed by metric.

Query

Draw from analysis/query

The chart calls the public analysis query API for total_tokens and cost, then renders grouped series directly.

ExtractorRuleSet

Create and publish through /v1/projects/:project_id/extractor-rule-sets.

demo
{
  "id": "ers_llm_gateway_finance",
  "name": "LLM Gateway Finance Metrics",
  "subject": {
    "default_type": "api_key",
    "type_path": "$.meta.subject_type",
    "id_path": "$.meta.subject_id",
    "required": true
  },
  "dimensions": [
    { "name": "provider", "path": "$.provider", "type": "string", "required": true },
    { "name": "model", "path": "$.model", "type": "string", "required": true },
    { "name": "customer_id", "path": "$.meta.customer_id", "type": "string", "required": true }
  ],
  "charge_item_extractors": [
    {
      "name": "input-tokens",
      "metric": "input_tokens",
      "path": "$.usage.input_tokens",
      "type": "number",
      "unit": "token",
      "skip_if_missing": true,
      "currency": "USD",
      "unit_price_expr": "dec('0')",
      "amount_expr": "dec_mul(dec_div(dec(item['quantity']), dec(item['pricing_unit'])), dec(item['unit_price']))"
    },
    {
      "name": "output-tokens",
      "metric": "output_tokens",
      "path": "$.usage.output_tokens",
      "type": "number",
      "unit": "token",
      "skip_if_missing": true,
      "currency": "USD",
      "unit_price_expr": "dec('0')",
      "amount_expr": "dec_mul(dec_div(dec(item['quantity']), dec(item['pricing_unit'])), dec(item['unit_price']))"
    },
    {
      "name": "cached-tokens",
      "metric": "cached_tokens",
      "path": "$.usage.input_tokens_details.cached_tokens",
      "type": "number",
      "unit": "token",
      "skip_if_missing": true,
      "currency": "USD",
      "unit_price_expr": "dec('0')",
      "amount_expr": "dec_mul(dec_div(dec(item['quantity']), dec(item['pricing_unit'])), dec(item['unit_price']))"
    },
    {
      "name": "total-tokens",
      "metric": "total_tokens",
      "quantity_expr": "dec_add(dec_add(dec(items['input_tokens']['quantity']), dec(items['output_tokens']['quantity'])), dec(items['cached_tokens']['quantity']))",
      "type": "number",
      "unit": "token",
      "currency": "USD",
      "amount_expr": "dec_add(dec_add(dec(items['input_tokens']['amount']), dec(items['output_tokens']['amount'])), dec(items['cached_tokens']['amount']))",
      "control": { "analytics_only": "true" }
    },
    {
      "name": "cost",
      "metric": "cost",
      "quantity_expr": "dec('0')",
      "type": "number",
      "unit": "event",
      "currency": "USD",
      "amount_expr": "dec_add(dec_add(dec_mul(dec_div(dec(items['input_tokens']['quantity']), dec(pricing_hints['cost.input_tokens']['pricing_unit'])), dec(pricing_hints['cost.input_tokens']['unit_price'])), dec_mul(dec_div(dec(items['output_tokens']['quantity']), dec(pricing_hints['cost.output_tokens']['pricing_unit'])), dec(pricing_hints['cost.output_tokens']['unit_price']))), dec_mul(dec_div(dec(items['cached_tokens']['quantity']), dec(pricing_hints['cost.cached_tokens']['pricing_unit'])), dec(pricing_hints['cost.cached_tokens']['unit_price'])))",
      "control": { "analytics_only": "true" }
    }
  ]
}
Profit analysis

The drawing layer stays simple.

The API returns metric buckets. The chart joins matching buckets, calculates profit and margin, then renders revenue and cost as two independent lines.

Start with your gateway data
revenue querymetrics: ['total_tokens'], measures: ['amount']
cost querymetrics: ['cost'], measures: ['amount']
bucket joingross_profit = total_tokens.amount - cost.amount
margingross_margin = gross_profit / total_tokens.amount