Live path
Point your existing wrapped client at the gateway. Bills as calls happen, and skips Cloudflare’s own cache hits on OpenAI and Anthropic.
Backfill path
Poll the gateway’s Logs API and bill Cloudflare’s own metered cost. Pass each entry’s id and re-running a window does not double-bill.
Live path
There is no Cloudflare-specific client to wrap. You wrap the same provider client as always, pointed at your gateway’s base URL instead of the provider’s.Some provider SDKs take the gateway auth header per call rather than at construction. The Mistral client, for example, wants
http_headers={"cf-aig-authorization": ...} on chat.complete, and takes server_url rather than base_url.Gateway cache hits are not billed, on two of the five clients
When the gateway serves a response from its own cache, it setscf-aig-cache-status: HIT and never calls the provider. Nothing was spent, so the SDK emits nothing for that response. This is implemented in the OpenAI and Anthropic wrappers only, and only for non-streaming calls.
Reading that header means going through the provider SDK’s raw-response accessor rather than the plain create call. In Python, .with_raw_response.create(...) then .parse(), which returns the identical object, so nothing downstream changes. With no gateway in the path the header is simply absent, making this a no-op on direct provider calls.
Streaming is excluded deliberately: it would need .with_streaming_response, which behaves differently and is not verified end to end. See Known limits for the full per-client picture.
Workers AI is priced from Cloudflare’s own catalog
Workers AI is Cloudflare’s own inference, reached through the gateway’s OpenAI-compatible/compat endpoint. Wrap an OpenAI-shaped client at that endpoint and use workers-ai/@cf/... model ids:
@cf/ is Workers AI’s naming convention and never a real OpenAI model, so those events are stamped provider: "workers-ai" and priced from Cloudflare’s catalog rather than OpenRouter.
That distinction is worth real money, not just correctness. Cloudflare’s catalog is the rate the gateway actually bills at. OpenRouter lists other hosts’ prices for the same open-weight models, and on a live check its figure for one model came out around 3.5x lower than what Cloudflare charged. Pricing Workers AI off OpenRouter would not be a naming mismatch, it would be the wrong number.
Workers AI pricing needs credentials. OpenRouter and the AWS price list are public. Cloudflare’s model catalog is not, so price mode needs a Cloudflare account id and an API token:Without both set, Workers AI prices are unavailable and the SDK falls back to token-count events, the same as any other price miss.The catalog is read for three units: input tokens, output tokens, and cached input tokens. A Workers AI model priced per image or per audio-minute, or with no price published at all, is absent from the table and falls back to token events too.
wrap() on a client pointed at gateway.ai.cloudflare.com, not at SDK init, and it runs on the background thread. wrap() normally happens well before your first completion, so the table is usually warm before it lands.
Backfill path
For usage that already happened, do not replay calls. Read the gateway’s own logs. Cloudflare reports a real meteredcost per entry, so there is no price lookup on our side at all.
This lives in a separate namespace from the wrapper adapters, because there is no client to instrument here. It is meant to run as a poller.
GET https://api.cloudflare.com/client/v4/accounts/{account_id}/ai-gateway/gateways/{gateway_id}/logs, paginated, authenticated with a Cloudflare API token. The list and single-entry endpoints return the same entry shape.
Because Cloudflare hands over one lump sum per call rather than a per-token-type split, this path emits a single llm_cost event with no token_type, and unit set to input plus output tokens. Splitting that lump proportionally would substitute a guess for the exact number you came here for. Live-path price mode, which does have per-token unit prices, emits one event per priced type instead. Both work with the same sum_agg metric on field_name: "unit" and a dynamic charge.
You do not need to filter cache hits out. Cloudflare reports a gateway cache hit with
tokens_in: 0, tokens_out: 0, and cost: 0, so a cached entry extracts as zero usage and bills zero on its own. Billing policy never has to branch on the cached flag. The flag is exposed in extras for your own reporting, not because the maths needs it.Two new emit() arguments
The backfill path is built on two arguments that price mode gained for exactly this case:
Attributing a log entry to a subscription
resolve_subscription() reads lago_subscription out of the entry’s metadata, which is populated from the cf-aig-metadata header on the original request. Set that header at call time and every log entry carries its own Lago subscription:
None when the header was never set. Deciding what to do with an unattributed entry (drop it, warn, bill a default) is left to you, deliberately.
What gets captured
From a Logs API entry:
Events are tagged
api: "cloudflare_gateway". The entry’s cached flag, step, and id land in CanonicalUsage.extras (as cached, step, and log_id), because the poller needs them: cached to decide whether Cloudflare served the request for free, log_id as the replay key.
Cloudflare names some providers differently from the SDK, so a few are mapped on the way in: google-ai-studio, google-vertex-ai, and vertex all become gemini; azure-openai and azureopenai become openai; workersai becomes workers-ai. Anything unrecognized passes through unchanged, which means it will miss on price and fall back to token events rather than being billed against the wrong rate card. Bedrock is deliberately left unmapped for that reason.
Unlike the provider-native adapters, this one never has to guess which model actually served a request. A Cloudflare log entry always reports the resolved model, so it is immune by construction to the alias-versus-snapshot mismatch that affects request-side model ids.
Choosing a path
Only the backfill path bills Cloudflare’s metered cost. The live path never sees it: no response header carries a per-call cost, so price mode there works exactly as it does for a direct provider call, off the public rate cards.
transaction_id, a backfilled one gets the event_id you pass. Lago treats them as unrelated events and bills the same call twice. Use the live path for current traffic and the backfill path only for windows the live path never covered.
Known limits
Cache-hit skipping covers OpenAI and Anthropic clients only, and only when not streaming. Readingcf-aig-cache-status means going through the provider SDK’s raw-response accessor, which the streaming path does not expose.
An older or custom OpenAI client without
with_raw_response also falls back to the plain path with no detection. If you rely on gateway caching for cost control, use the backfill path — it handles cache hits for every provider, streaming included, because Cloudflare logs them with zero tokens and zero cost.
Cloudflare’s metered cost excludes reasoning tokens. It is exact on input, output, cache reads, and cache writes, but additive reasoning tokens are left out. On two measured thinking-heavy Gemini calls the reported cost came to roughly 4% of what Google actually charged — 22.8× and 39.6× under. For models that do not reason, the backfill figure is the one to bill on; for a thinking-heavy workload, meter it in token mode and price the reasoning tokens yourself.
Cloudflare does not normalize usage_metadata key casing. It passes through whatever convention the underlying provider used: Anthropic and OpenAI entries come back snake_case (input_cached_tokens), while a captured Gemini entry used camelCase (reasoningTokens). The adapter checks both forms for every field it maps, but that is observed behavior across two providers, not a documented Cloudflare guarantee — a provider using a third convention could report tokens the adapter reads as zero. If you add a provider to your gateway, verify one log entry against the counter in Lago before trusting the rollup.
Next steps
Configuration reference
Every config knob, in both SDKs.
Bill in dollars
How price mode and the
llm_cost metric work.