anthropics / anthropics/claude-code

[BUG] Spend meter's rate model can't express real prices: pricing.multiplier is global and capped at 1, and inference_geo is ignored

Aperta
#92,751 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:cost bug
Lingua principale
Python
Stelle
145k
Fork
23.1k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

### Preflight Checklist

- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code

### What's Wrong?

The Claude apps gateway's spend meter keys a rate on `(upstream, model)`, but the price a provider actually charges also depends on the inference **geography** and, first-party, the request **mode**. Three documented gaps follow, all in the direction that under-counts spend, so caps admit more than configured.

**1. `pricing.multiplier` cannot express a premium.** `> 1` is rejected at boot (`Number must be less than or equal to 1`, at `pricing.multiplier`). But premiums are exactly what a data-residency deployment needs:

| Upstream | What triggers the premium | Premium | Encoded in the model ID? |
| --- | --- | --- | --- |
| `bedrock` | `us.` / `eu.` / `au.` / `jp.` inference profiles, or a bare in-region model id | +10% | **Yes** — the prefix |
| `vertex` | regional and multi-region endpoints | +10% | Yes — the endpoint |
| `anthropic` (first-party) | `inference_geo: "us"`, per request or via a workspace `default_inference_geo` | 1.1× | **No** |
| `anthropicAws` (Claude Platform on AWS) | the same `inference_geo: "us"` | 1.1× | **No** |
| `foundry` | the US Data Zone Standard deployment type | 1.1× | No |

Sources are Anthropic's own: [Data residency pricing](https://platform.claude.com/docs/en/about-claude/pricing#data-residency-pricing) — *"specifying US-only inference through the `inference_geo` parameter incurs a 1.1x multiplier on all token pricing categories, including input tokens, output tokens, cache writes, and cache reads"* and *"Regional and multi-region endpoints include a 10% premium over global endpoints"* — plus [Claude in Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-in-amazon-bedrock#regions): *"Global: … No pricing premium. Regional: … carry a 10% pricing premium over global endpoints."* AWS's [Bedrock pricing page](https://aws.amazon.com/bedrock/pricing/) publishes two Claude tables, "Global Cross-region Inference" and "Geo and In-region Cross-region Inference", the second exactly 1.1× the first on every column (us-east-1 and ap-southeast-2 checked 2026-09-07; Opus 5 $5.00/$25.00 vs $5.50/$27.50).

The built-in table is list price, i.e. the global rate, so a residency deployment under-counts real spend by ~9% and a $500/month cap admits roughly $550. Systematic, one-directional, and not visible in the config, the boot log, or the audit events.

**2. `pricing.multiplier` is global to the gateway.** There is no per-upstream form — checked against 2.1.229, all rejected at boot:

| Attempted form | Result |
| --- | --- |
| `multiplier` inside an `overrides` row | `Unrecognized key(s) in object: 'multiplier'` |
| `pricing.upstreams: { bedrock: { multiplier: 0.9 } }` | `Unrecognized key(s) in object: 'upstreams'` |
| `pricing.multipliers: { bedrock: 0.9 }` | `Unrecognized key(s) in object: 'multipliers'` |
| `pricing.markup: 1.1` | `Unrecognized key(s) in object: 'markup'` |

Rates are per-upstream (`overrides[].upstream`), but the multiplier isn't. Multi-upstream failover is a first-class feature, and the natural configurations put one multiplier over two different rate cards: an in-region primary with a global fallback (one tier premium, one not), or `bedrock` alongside `anthropic`, where each side carries its own separately negotiated discount. Applying one number to both is wrong for at least one of them.

**3. Nothing consumes `inference_geo`.** On `anthropic` / `anthropicAws` / `foundry` the premium is per-request, so `overrides` — keyed `{upstream, model}` — cannot price both geos: two requests to the same model on the same upstream differ only by their `inference_geo`. An org with a US-only workspace default is metered ~9% light on every request with no supported correction.

The meter is already handed the discriminator. The response `usage` object carries `inference_geo`, and the pinned binary's usage schema parses it (`inference_geo: nullable().optional()`, alongside `service_tier` and `cache_creation`) and threads it through usage merging. The metering path simply doesn't read it.

On `bedrock` and `vertex` the tier is in the model ID, so `overrides` rows are a workaround there — but a verbose one: four rates × N models, re-derived on every price change, and a model that isn't listed silently falls back to list price, including one added by a version bump.

**The two levers also conflict.** The [config reference](https://code.claude.com/docs/en/claude-apps-gateway-config#pricing) notes an override row *"includes the higher fast mode rate, so fast and standard requests meter at the same four rates."* Fast mode on Opus 5 is $10/$50 against $5/$25 standard — 2×, first-party only. So using rows to correct a geo premium on a first-party upstream halves fast-mode metering: fixing one dimension by breaking another. That is the shape of the problem. The rate model needs another axis, not more rows.

### What Should Happen?

Any one of these helps, and the first two are small:

1. **Allow `multiplier > 1`**, or add a sibling `markup`, so a flat tier premium is one line instead of a per-model table.
2. **Allow `multiplier` per upstream** — as a field on an `upstreams[]` entry, on an `overrides` row, or as a `pricing.upstreams` map — so a failover stack or a mixed-provider deployment can price each upstream on its own rate card and its own discount.
3. **Apply the documented 1.1× when the response reports non-global inference**, from the `usage.inference_geo` the meter already receives. This is Anthropic's own published pricing rule, not third-party pricing the gateway would have to track, and it is the only option that fixes the first-party case at all.

Minor, same theme: an `overrides` row carries one `cache_write` rate, but 5m and 1h cache writes are priced apart (geo-tier Opus 5: $6.875 vs $11.00), so a row can encode only one of the two.

### Environment Info

- Claude Code version on the gateway server: **2.1.229** (`linux-x64`, run as `claude gateway --config gateway.yaml`)
- Upstream: Amazon Bedrock, `auth: {}` (task-role credentials via the AWS default chain), `global.anthropic.*` inference profiles
- Deployment: ECS Fargate behind an internal ALB, RDS for PostgreSQL store, `admin:` block enabled
- Every schema claim above is a boot-time result from that binary; the config-schema check runs before Postgres, so `could not connect to Postgres` confirms the schema passed. Prices read from Anthropic's pricing docs and AWS's pricing feed on 2026-09-07.

### Additional Context

Runtime metering isn't verified here — proving the meter charges the global rate for a `us.`-profile request needs a live gateway with a store. Everything asserted above is either a boot-time schema result or a published price.

Prior report #83690 raised the Bedrock half of this and asked for tier-aware rates, operator-supplied rate overrides, or a documented disclosure of the skew. **2.1.227 shipped the overrides** — the `pricing:` block, general enough to cover a negotiated discount on any provider, which is the right shape for that ask; thank you for it. That issue was then auto-closed by the stale bot before the rest was triaged, and the direction that survived is the consequential one: caps that admit ~10% more than configured. Filing this as the narrower, provider-general follow-up.

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Start from the pricing config path used by `claude gateway --config gateway.yaml`, especially validation of `pricing.multiplier` and `overrides`. Then trace the spend-metering path that receives response `usage.inference_geo`. Done means one supported pricing correction path is accepted by the schema and reflected in metering for the described geo-premium cases.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
aws, yaml
Ambito
backend, devops
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.