HookGet Open dashboard

Measuring spend through an AI gateway

Unified billing moves your spend off the provider's ledger and onto the gateway's. Here is what that breaks, how the connector keeps two ledgers from ever double-counting one request, and what a promotional rate looks like when it ends.

In short

  • A model consumed through a gateway on unified billing does not appear in the OpenAI or Anthropic connectors — that spend is on the gateway ledger.
  • The ai-gateway source reads the gateway's reporting endpoints for cost and tokens by model, provider, user and tag, plus the prepaid credits balance.
  • Every event declares provider: "ai-gateway" with the vendor as upstream_provider, so running both ledgers can never double-count one request.
  • Money lives only on llm.cost_reported; usage events carry tokens. Summing cost is the bill, not a multiple of it.
  • llm.credits_reported carries the balance — the failure mode unified billing introduces is a prepaid balance running out and stranding production.
  • Buckets are read about 90 minutes settled, because a gateway keeps writing into the current one.

What breaks without a gateway source

SetupWhat the provider connectors seeWhat is missing
All traffic direct to OpenAI/AnthropicEverythingNothing
All traffic through a gateway, unified billingLittle or nothing — the spend is on the gateway ledgerThe whole bill
Some direct, some through a gatewayOnly the direct halfThe gateway half, silently

The double-count trap, designed out

The obvious implementation labels gateway traffic with the upstream vendor, because that is who ran the model. It is also how one request gets summed twice the moment a customer runs both paths. The ledger is therefore always the gateway, and the vendor is context:

{
  "eventType": "llm.cost_reported",
  "attributes": {
    "provider":          "ai-gateway",   // the ledger the money is on
    "upstream_provider": "openai",       // who ran the model
    "model":             "gpt-5.6-sol",
    "tag":               "support-triage"
  },
  "metrics": { "cost_usd": 4.00 }
}

The AI spend template ships a spend by ledger widget that groups on attributes.provider for exactly this reason: the two ledgers side by side, summed on purpose rather than by accident.

Promotions, and the day they end

A discounted model is easy to misread. Three things move at once — the unit price, the volume, and the mix of models — and only one widget separates them:

What movedTotal spend saysCost per 1M tokens says
A discount startedwent down, maybea clear step down — the rate changed
A discount endedwent upa clear step up on the day, volume unchanged
Traffic grewwent upflat — this is volume, not price
Traffic moved to a pricier modelwent upup, with spend by model naming the culprit

The ratio a promotion does not change. Output tokens cost several times input, and a cache read costs roughly an eighth of an input token. A headline discount that doubles your output volume is a wash; a cache hit rate that climbs ten points is a saving you keep after the promotion ends. Both are widgets in the template.

Credits, and the failure unified billing introduces

Prepaid credits fail differently from an invoice: they run out, and production stops. The connector reads the credits endpoint on every sync and emits llm.credits_reported with the balance, so the number is on the dashboard as a level rather than discovered as an outage. A gateway running in bring-your-own-key mode has no credits endpoint; that is not an error and the sync continues.

Connect it

curl -X POST https://api.hookget.com/v1/sources \
  -H "authorization: Bearer $HOOKGET_KEY" \
  -d '{
        "provider": "ai-gateway",
        "secret": "<read-only reporting key>",
        "sync_interval_sec": 900,
        "config": { "bucket_width": "1h", "group_by_tag": true }
      }'

Config is validated when you save it: an unknown key is refused rather than ignored, a non-HTTPS base URL is refused, and a bucket width outside 1h or 1d is refused. A rejected credential backs off and pauses with a readable health message instead of retrying forever.

Where this is, on 28 August 2026. The gateway connector, the dashboard template, the mapping layer and the per-person and per-department summaries are live: a gateway user is mapped to a named team member and a department, and the API reports spend by person and by department, gated to admins. What is not built yet is the billing — the add-on catalogue is defined in code and nothing charges for it — along with budgets, forecasts and a finance-system export.

Questions

Why does a gateway need its own connector?

Because the money moves. The OpenAI and Anthropic connectors read those vendors' organisation reports. On unified billing — which is what a gateway promotion normally requires — the spend is on the gateway's ledger, not the provider's. Without a gateway source, the bill HookGet shows you is missing that traffic entirely, and nothing says so. That is the hole this closes.

What is the double-count trap?

Running some traffic direct and some through a gateway is normal. If the gateway connector labelled its events with the upstream vendor's name, one request could be summed on both ledgers and the total would be quietly, confidently wrong. So every event from the gateway declares provider: "ai-gateway", and the vendor behind it rides as upstream_provider. Two ledgers, always separable; a dashboard sums one, the other, or both — deliberately.

What happens when a promotional rate ends?

Your bill steps up while traffic stays flat, and the widget that shows it is cost per 1M tokens — the rate isolated from volume. Total spend cannot separate a price change from a usage change; that formula can. The step appears the day after the promotion ends, in the same place the discount appeared the day it started.

Do you read the gateway request logs?

No, and that is a design decision worth stating. A gateway's request logs contain prompts and completions. The connector reads the aggregate reporting endpoints and the credits endpoint only, which is why these events carry containsPii: false honestly.

What key does it need?

A read-only reporting key. Nothing here requires a key that can spend, and one should never be issued for it. The key is stored encrypted, used only by the sync worker, and returned by no API route.