Reconcile Before You Charge Back: Client Estimate, Gateway Meter, Provider Invoice
AI chargeback starts with reconciliation: price client estimate, gateway meter and provider invoice on one rate file, and block runs that don't tie out.
Go deeper. Build your own.
Finance wants one line per team on the third working day of the month. The provider sends one line per credential, and behind a Claude apps gateway there is one credential for everyone: Anthropic’s own docs say the provider’s bill “attributes everything to that credential, not to individual developers.” Every AI chargeback you send is a claim about how to cut that single line, and three meters in your fleet will each suggest a different cut.
This runbook makes the cut defensible before the first internal bill goes out. By month end each lane has a three-column worksheet (client estimate, gateway meter, provider invoice) priced against one dated, versioned rate file. Totals reconcile at multiplier 1.0. Team charges are cut from the invoice, never from a meter. A variance register explains every gap, and anything unexplained above your tolerance stops the run.
Chatbots suggest; agents act, and acting agents spend with nobody reading along. One developer’s afternoon can hold a dozen subagents, two compactions and MCP calls across three plugins, all on the shared credential. The meters watching that spend were built as brakes. Billing people from a brake is how an internal chargeback program loses the room in its first month.
Sep 10–15: Claude Code’s gateway grows chargeback controls
Three Claude Code releases in six days turned the Claude apps gateway into something that looks like a billing system. Per the Claude Code changelog, 2.1.268 (Sep 10) pushes the gateway’s rates down to clients: “with pricing: set in gateway.yaml, signed-in Claude Code clients receive the same rates through managed settings, so /cost and telemetry match the spend meter.” /cost is an alias of /usage, which is what the docs call it now. The rates travel as the modelPricing managed setting, and the settings reference says clients apply it from v2.1.242.
2.1.271 (Sep 14) raised the ceiling on markup, allowing a multiplier above 1, “up to 10, in the modelPricing managed setting and the Claude apps gateway pricing block, for marked-up internal chargeback rates.” 2.1.273 (Sep 15) added opt-in gateway hint headers (CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 sends request class, agent type, previous tool durations and compaction markers) and changed OTEL_LOG_TOOL_DETAILS=1 “to also include real agent, skill, plugin and MCP server names on cost and token metrics.”
The docs are more candid than the release notes. The spend-limits page calls the meter’s amounts “USD estimates, a circuit breaker rather than an invoice; for billing, reconcile against your provider’s usage reporting.” The gateway configuration reference adds the sentence every chargeback memo should open with: “The multiplier doesn’t change what the upstream provider charges for the requests.”
Screenshot: Claude Code Docs, “Claude apps gateway configuration - Claude Code Docs” (undated), captured Sep 21, 2026.
Other meters moved the same week. GitLab 19.4 (Sep 17) turned its credit usage export into a ZIP of two CSV files, keeping the daily summary and adding a per-event file with “one row for each billable event” on Premium and Ultimate (release notes). GitHub’s Copilot Auto tiers (Sep 14) restated that “Usage is charged based on the model auto selects, regardless of tier” (changelog), the routed-model billing that the hosted-router buyer’s guide takes apart.
Three readings of one month, three different blind spots
Each column answers a different question. The client estimate is what Claude Code computed from its own token counts and whatever rates reached it; /usage, the status line, the Agent SDK’s total_cost_usd, --max-budget-usd and OTel cost metrics all use modelPricing when it is set. The gateway meter is what the gateway added to its counters after each response, already multiplied. The provider invoice is what the provider charged the shared credential. None is broken by design. Each is blind to something the others see, and the vendor docs name most of those blind spots, which is where Step 5 starts.
The adjacent work is already live. Three meters: credits, tokens, sandbox inventories meters and caps them before the spend, down to a rates_version per job; this piece starts after the spend, when three readings of it have to agree. Cost anomaly alerts for agent fleets gives the invoice-versus-meter gap one paragraph. Here it gets a worksheet, an allocation rule and a block.
Step 1: Price every column against one dated, versioned rate file
Before any column holds a number, write down what it will be priced at. One rate file per lane per billing period, versioned, with a UTC effective window and the name of whoever changed it. The file is yours; the gateway’s pricing block is one input, alongside your provider contract and the list prices you checked. Seat and plan lanes record their plan terms instead of per-token rates; how token plans are priced covers reading them.
# rates/claude-gateway-eng/2026-10.yaml (illustrative shape; your own format)
rate_file: claude-gateway-eng@2026-10.v1
effective_from: "2026-10-01T00:00:00Z" # gateway monthly reset, 00:00 UTC on the 1st
effective_to: "2026-11-01T00:00:00Z"
invoice_period: "2026-10 (UTC calendar month; confirm against the contract)"
sources:
provider_contract: "<contract id, amendment date>"
list_prices: "<provider price page, fetched date>"
gateway_pricing_sha: "<hash of the gateway.yaml pricing block>"
overrides: # mirror of gateway pricing.overrides rows, USD per MTok
- {upstream: "<name>", model: "<id>", input: "<usd>", output: "<usd>", cache_read: "<usd>", cache_write: "<usd>"}
not_in_client_overrides: [fast-mode surcharge, US-only-inference rate]
web_search_usd_per_request: 0.01 # list price; the multiplier still applies
unknown_model_tier_usd_per_mtok: {input: 5, output: 25}
gateway_multiplier: 1.15 # what the counters were multiplied by
chargeback_markup: 1.15 # what you publish and apply to allocations
changed_by: "<name>"
changed_at: "2026-09-30T18:02:11Z"
Two rules make the file load-bearing. Change the multiplier and the rate file only at the monthly UTC reset, lined up with the invoice period; the gateway’s caps reset on UTC calendar boundaries (daily at 00:00, weekly on Monday, monthly on the first). And log every change with a UTC timestamp, even a change you plan to revert, because Step 3 depends on that log.
Step 2: Fill the three-column worksheet for every lane
Each lane gets one sheet per month. Pull each column from its own source and convert to list terms before you compare anything.
| Column | Where it comes from | Convert before comparing |
|---|---|---|
| Client estimate | OTel cost metrics from every client (with OTEL_LOG_TOOL_DETAILS=1 for agent, skill, plugin and MCP names); Agent SDK total_cost_usd for SDK lanes |
Divide events from v2.1.271+ clients by the multiplier; older clients already show costs without it |
| Gateway meter | Per-principal period-to-date counters (the gateway’s spend table, or GET /v1/organizations/spend_limits/effective) |
Counters are in cents and already multiplied: divide by 100, then by the multiplier in force |
| Provider invoice | The provider’s usage reporting for the shared credential; for cloud-provider lanes, the cloud bill | None; this is the anchor |
A filled sheet for one lane looks like this. The numbers are illustrative.
| claude-gateway-eng, Oct 2026 (illustrative) | Client estimate | Gateway meter (÷ 1.15) | Provider invoice |
|---|---|---|---|
| Total at list terms | $38,900 | $41,200 | $41,650 |
| Gap to invoice | −$2,750 | −$450 | — |
| Explained by register lines | −$2,410 (lines 1, 2, 4) | −$390 (lines 8, 9) | — |
| Unexplained | −$340 (0.8%) | −$60 (0.1%) | — |
Read the sheet top to bottom. The client column is expected to drift most, because it depends on which rates reached which session and when. The gateway column should sit close to the invoice. If the gateway column drifts more than the client column, suspect the meter first: an outage window, an unknown model or a truncated export.
Step 3: Reconcile totals at multiplier 1.0, and never across a rate change
Reconcile totals in list terms, lane by lane. The invoice has no per-developer rows, so there is nothing below the total to reconcile against; per-principal numbers are for allocation, which is Step 4.
The gateway column needs one piece of arithmetic, and it only works under one condition. The spend-limits docs say the meter multiplies each amount by pricing.multiplier before adding it to the counters, so a counter holds marked-up cents. A period-to-date counter divides back to list terms only if one multiplier held for the whole period. The docs don’t cover splitting a counter across a mid-period change; that limit follows from how the counter is built, and a change log can’t repair it, because the counter kept no record of which cents arrived at which rate.
Illustrative. A mixed-rate counter has no single divisor that recovers list spend. Only the snapshot split lands on the true figure.
If a change must land mid-month, do this:
- Snapshot every principal’s period-to-date spend immediately before the change takes effect.
- Apply the change, then snapshot again as soon as it is live, and record both UTC timestamps in the rate-file log.
- At month end, divide each segment (start to first snapshot, second snapshot to close) by its own multiplier and add them. Requests that landed between the two snapshots go in the variance register as their own line.
- Without those snapshots, mark the lane mixed-rate and unreconstructible for the month. Allocate that month from client telemetry, which is event-level and timestamped and can be divided back per event against the change log, and label every charge an estimate.
# illustrative: snapshot per-principal to-date spend around a rate change
# authenticate the way your gateway's admin API is configured
ts=$(date -u +%Y%m%dT%H%M%SZ)
curl -s "$GATEWAY_URL/v1/organizations/spend_limits/effective" $ADMIN_AUTH \
> "snapshots/claude-gateway-eng/effective-$ts.json"
Step 4: Allocate the invoice, not the meter
The amount you charge back is the invoice. The meter decides only the shares. For each principal, take the gateway counter in list terms, roll principals up to teams with your directory mapping, and give each team its share of the invoice total. Then apply the markup you published.
team charge = invoice total × (team gateway spend at list ÷ lane gateway spend at list) × published markup
| Team (illustrative) | Gateway spend at list | Share | Allocated invoice | Charge at 1.15 markup |
|---|---|---|---|---|
| payments | $16,480 | 40% | $16,660 | $19,159 |
| platform | $12,360 | 30% | $12,495 | $14,369 |
| search | $8,240 | 20% | $8,330 | $9,580 |
| data | $4,120 | 10% | $4,165 | $4,790 |
| Lane total | $41,200 | 100% | $41,650 | $47,898 |
The run asserts one invariant before anything is sent: the sum of team charges equals the invoice total times the markup, within rounding. A run that fails it has billed from a meter somewhere.
Screenshot: Claude Code Docs, “Claude apps gateway spend limits - Claude Code Docs” (undated), captured Sep 21, 2026.
Finer splits come from the client side. Repo, agent, skill, plugin, MCP server and request class all live in client telemetry and hint headers, not in the invoice, and an invoice line can’t be joined to one session (vendor-hosted agent evidence covers why). Show those splits as a breakdown under each team’s charge, labelled estimate, scaled to sum to the team’s allocated amount. Never bill a repo line as if the provider had metered it.
Step 5: Seed the variance register from the vendor’s own divergence lines
Every documented reason the three columns disagree becomes a numbered register line, with the column it moves and how you detect it. Start from what the docs already admit, so the first month’s gaps have names before they have owners.
| # | Documented divergence | Column it moves | Detect with | Source |
|---|---|---|---|---|
| 1 | A session reports list price until its settings fetch confirms modelPricing |
Client | Cost events that predate the fetch, per session | Settings reference |
| 2 | Clients below v2.1.242 ignore modelPricing; below v2.1.271 they ignore a multiplier above 1 |
Client | Client version on every cost event | Settings reference, config |
| 3 | A developer who matches no policy gets no managed settings and stays at list price | Client | Principals in gateway counters with no policy match | Config |
| 4 | Override rows are applied as written, without the fast-mode surcharge or the US-only-inference rate | Client | Fast-mode or US-only usage on the invoice | Settings reference |
| 5 | A rate that only a failover upstream charges stays on the gateway | Client vs gateway | Requests served by the failover upstream | Config |
| 6 | Proxies that strip or rewrite headers push auto mode onto billed client-side classifier requests | Invoice | Classifier request volume after a proxy change | Classifier billing |
| 7 | Cloud-provider usage is invisible to Anthropic’s analytics dashboards and Analytics API | Invoice lives on the cloud bill | Cloud lanes with no vendor-analytics rows | Costs |
| 8 | Client aborts are metered at a floor of about four characters per output token | Gateway | Abort count per principal | Spend limits |
| 9 | Enforcement fails open by default during a Postgres outage, so requests keep flowing while metering is impaired | Gateway (low) | Outage windows; enforcement.fail_closed_on_error value |
Spend limits |
| 10 | Unmatched models fall to a $5/$25 per MTok unknown-model tier | Gateway | Model IDs with no override and no list match | Spend limits |
| 11 | Web search is metered at the $0.01 list price, with the multiplier applied | Gateway | Web-search request count | Config |
| 12 | LiteLLM’s legacy /spend/logs stops at the 10,000 most recent rows |
Neutral gateway column (low) | x-litellm-spend-logs-truncated: true header |
LiteLLM |
Line 6 comes from the auto-mode classifier billing page, line 7 from the costs page, which says Claude Code “does not send metrics from your cloud back to Anthropic.” Lines 8 to 10 come from the spend-limits page linked above; line 12 is LiteLLM’s, and Step 7 covers it.
Every edge of the triangle is a register line waiting to be quantified. The rate file in the middle prices all three corners.
Each month, every register line gets a dollar figure or a zero. Whatever is left is the unexplained variance, and a register line that explains the same gap three months running is a configuration fix you haven’t made yet.
Step 6: Set a tolerance, block the run, canary every change
Pick a tolerance per lane before the first run, not after you see the number. A reasonable starting point is the larger of a small percentage of the invoice and a fixed dollar floor, tightened as the register matures. Any unexplained variance above tolerance blocks the chargeback run for that lane. No partial send, and no promise to true it up next month.
The block has to fail closed. If the reconciliation job crashes, a column is missing, the invoice hasn’t arrived or a snapshot file is empty, the lane’s run is blocked with the reason written down, and finance gets a late bill instead of a wrong one. The spend caps are a separate brake and keep running in the meantime. Remember that the caps fail open on a Postgres outage unless you set enforcement.fail_closed_on_error: true, which trades developer availability for a hard stop.
After every rate, contract or CLI change, send one canary request through a test principal. Record the client’s /usage figure for that session, the gateway counter delta divided back to list terms, and the provider’s usage line once it posts. Three numbers for one request tell you which column moved before a month of drift does. CLI bumps get the same treatment as the rest of the fleet’s upgrades; the CLI upgrade canary owns that replay.
Step 7: Two honest columns where there’s no gateway
Lanes that bill you directly have no independent meter, so the worksheet has two columns: the vendor’s usage report and the invoice. Say so in the sheet’s header. A two-column check can detect a variance in total but rarely attribute it.
| Lane | Usage column | What the docs give you | What they don’t |
|---|---|---|---|
| GitLab credits (Premium, Ultimate) | 19.4 per-event CSV: product, flow type, session, user, namespace, project, credits used, token counts | One row per billable event; background export, download link by email | No price per credit, so price rows with your contract’s rate |
| Cursor (Teams, Enterprise) | Usage pools in editor settings and the usage dashboard | Auto bills at the list price of the routed model; a $0.25 per million Cursor Token Rate on third-party models, BYOK included | No documented per-request export of the routed model |
| Copilot | Usage billed through Auto | Charged at the model Auto selects, regardless of tier; paid subscribers keep a 10% discount on Auto usage; the model is visible per response in the UI | No documented export or API log of routed models |
| LiteLLM (your gateway) | /global/spend/report by team, customer, key or user; /spend/logs/v2 |
Per-key, per-user and per-team spend in LiteLLM_SpendLogs; a debugging workflow for “Cost does not match your provider bill?” |
No CSV export on the cost-tracking page |
Cursor’s pricing page is the rate-file source for its lane: the Token Rate belongs in the file as its own line, not folded into model prices. GitLab’s export is in credits, and translating credits into tokens is its own discipline; credit versus token meter dialects covers it.
Where you run a vendor-neutral gateway such as LiteLLM, it becomes the middle column. Pull from /spend/logs/v2; the LiteLLM cost-tracking docs say the legacy /spend/logs truncates to the 10,000 most recent matching rows and flags it with a response header, so a reconciliation built on it under-counts in exactly the months that matter.
Step 8: Publish the markup before it goes live
A markup is a price change for every team, and on the Claude apps gateway it is also a cap change. With an admin: block, the gateway applies the multiplier to spend limits, so developers reach their caps sooner at 1.15 than at 1.0. Publish the new rate, the effective date and the cap impact at least one billing period ahead.
# gateway.yaml (illustrative; see the gateway configuration reference for exact keys)
pricing:
multiplier: 1.15 # greater than 0, at most 10; change only at the monthly UTC reset
Then check who will see it. Clients below v2.1.271 show costs without the multiplier, and a developer who matches no policy sees list price, so the same session can show two different figures on two laptops. Put the version floor in the announcement, and expect the first disputes to come from that gap rather than from the math.
AI chargeback failure modes and the signal each one leaves
The mixed-rate month. The multiplier moves on the 12th to cover a contract change, and nobody takes snapshots. Signal: two rate-file versions inside one invoice period. Response: mark the lane unreconstructible, allocate from timestamped client telemetry as an estimate, and move the next change to the reset.
Billing from the brake. A script exports the gateway’s marked-up counters and sends them as the bill. Signal: charges don’t sum to invoice × markup. Response: the Step 4 invariant, enforced in code before send.
The fail-open gap. The gateway’s Postgres is down for four hours; requests keep flowing because enforcement fails open, and the provider keeps billing every one of them. Signal: gateway column low by roughly the outage window’s run rate. Response: register line 9, and a decision on enforcement.fail_closed_on_error made on purpose.
The truncated export. The neutral gateway column comes up short in the busiest month. Signal: the x-litellm-spend-logs-truncated header on any pull. Response: /spend/logs/v2, and a job that fails when the header appears.
The version floor nobody announced. Half the fleet’s clients predate v2.1.271, /usage shows unmarked costs, and teams dispute charges 15% above what their developers saw. Signal: a client-version histogram weighted by spend. Response: publish the floor with the markup, and treat line 2 as a rollout task.
A split presented as a meter reading. A team is charged for a repo line that came from telemetry. Signal: a dispute you can’t answer with the invoice. Response: label splits as estimates and scale them to the allocated total.
AI chargeback reconciliation belongs in the fleet’s operating layer
The worksheet, the register and the block are operating-layer work. No vendor’s usage view does them, because each view sees one vendor’s meter and your fleet runs several: a gateway lane, a seat product that bills by credit, a router that bills by whatever model it picked. The reconciliation is where those meters meet, which makes it part of the same layer as the kill switch and the approval queue, the layer a multi-agent command center runs. The token bill is also only part of what a lane costs you; the operating bill versus the token bill prices the rest.
Charge back what you can explain. Everything else waits a month, with its reason written down.
FAQ
Does the Claude apps gateway multiplier change what the provider bills?
No. The gateway configuration docs say the multiplier doesn’t change what the upstream provider charges for the requests. It raises what the gateway meters and, with an admin block, how quickly developers reach their spend caps. Reconcile against the invoice in list terms, then apply your published markup to each team’s allocation.
How do you allocate a shared AI provider invoice to teams?
Start from the invoice total, not a meter total. Divide each principal’s gateway counter back to list terms, roll principals up to teams, and give each team its share of the invoice. Repo, agent and request-class splits come from client telemetry, so show them as labelled estimates under each team’s charge.
Sources
- Claude Code changelog: 2.1.268 (Sep 10) rates to clients, 2.1.271 (Sep 14) multiplier up to 10, 2.1.273 (Sep 15) hint headers and tool names on cost metrics
- Claude apps gateway spend limits: circuit breaker, shared credential, cents counters, UTC resets, abort floor, fail-open metering
- Claude apps gateway configuration: pricing: multiplier range, admin-block caps, failover rates, client version floors
- Claude Code settings reference: modelPricing: list price until the settings fetch lands, surcharges not added to override rows
- Claude Code costs: cloud-provider usage outside Anthropic’s analytics
- Auto mode classifier billing: header-stripping gateways trigger billed classifier fallbacks
- GitLab 19.4 release notes: per-event credit usage export (Sep 17, 2026)
- LiteLLM spend tracking: spend by key, user and team;
/spend/logs/v2; legacy truncation at 10,000 rows - GitHub changelog: Copilot auto model selection tiers: usage charged at the model Auto selects (Sep 14, 2026)
- Cursor models and pricing: Auto at routed-model list price, Cursor Token Rate
