---
slug: claude-code-on-cloudflare-ai-gateway
title: "Claude Code on Kimi, GLM or Grok through your own Cloudflare account"
kind: essay
status: published
hash: 83926ef0d22cbbb1fe564a7e61d7360b70550cc62ba9d0105cb0dd7777720dc6
created: 2026-07-26T01:21:33.461Z
updated: 2026-10-08T11:33:19.985Z
summary: "Step-by-step: run the Claude Code CLI against Kimi K2.7 Code, GLM-5.2, Grok or Claude through your own Cloudflare AI Gateway. Prerequisites, exact commands, the two settings that cut a turn from $0.21 to $0.0044, every alternative route, and what breaks."
tags: ["tooling","claude-code","kimi","glm"]
above: []
below: []
adjacent: []
nested: []
---
# Claude Code on Kimi, GLM or Grok through your own Cloudflare account

*Step-by-step: run the Claude Code CLI against Kimi K2.7 Code, GLM-5.2, Grok or Claude through your own Cloudflare AI Gateway. Prerequisites, exact commands, the two settings that cut a turn from $0.21 to $0.0044, every alternative route, and what breaks.*

A Claude Code turn that costs $0.0044 instead of $0.21, running Kimi K2.7 Code, billed by Cloudflare, with no Anthropic key and no Moonshot key in the configuration. That is the outcome this page produces. Follow the steps in order and it takes about twenty minutes.

Two reasons to want it. The money: the same work priced at $0.95 per million input tokens instead of $3 to $15, on one invoice you already receive. The obedience: open-weight models such as Kimi K2.7 Code and GLM-5.2 follow an explicit instruction more literally than Claude does, and if your work depends on instructions being followed exactly rather than improved upon, that difference is the point. Both reasons are quantified further down, with the arithmetic shown.

## Evidence status

**Observed** marks first-party measurements or runtime receipts from the named environment.
**Derived** marks arithmetic calculated from cited inputs. **Specified** marks vendor or standards
documentation. **Implemented** and **deployed** name code and live-state evidence, respectively.
**Reproduced** means the stated procedure was rerun. **Externally attested** marks operator reports;
those reports show that an experience occurred, not that it is universal.

## Every term on this page, defined

| Term | What it means here |
| --- | --- |
| Claude Code | Anthropic's command-line coding agent. It reads and writes files, runs shell commands, and calls tools. It is a client program: the model it talks to is whatever address you configure. |
| Anthropic Messages API | The request and response format Claude Code speaks: `POST /v1/messages` with a JSON body. Defined at [platform.claude.com/docs/en/api/messages](https://platform.claude.com/docs/en/api/messages). Full explanation: [What the Anthropic Messages API is](/a/what-is-the-anthropic-messages-api). |
| Chat Completions | The request and response format most other providers speak: `POST /v1/chat/completions`. Different field names, different streaming events. Not interchangeable with the Messages API without translation. |
| Cloudflare AI Gateway | A proxy in front of AI providers that adds logging, caching, rate limits, retries and billing. Setup: [How to create a Cloudflare AI Gateway](/a/cloudflare-ai-gateway-setup). |
| Workers AI | Models Cloudflare hosts and bills directly, named `@cf/author/model`. Includes Kimi K2.7 Code and GLM-5.2. Details: [Workers AI for coding models](/a/workers-ai-coding-models). |
| Unified Billing | Cloudflare pays the upstream provider and bills you, so no provider API key appears in your configuration. Arithmetic: [Cloudflare Unified Billing](/a/cloudflare-unified-billing). |
| BYOK | Bring Your Own Keys. You store the provider's key in the gateway instead of sending it with each request. |
| MCP | Model Context Protocol. How extra tools are attached to a coding agent. Reference: [MCP, from its own documents](/a/what-is-mcp). |
| Tool search | A client setting that stops every MCP tool definition being sent in every request. Cost impact: [Why MCP tool schemas are most of your bill](/a/mcp-tool-search-cost). |
| Translator | A program that accepts Anthropic Messages requests and converts them to Chat Completions, then converts the answer back. Also called a shim or a proxy. This page publishes one. |
| Turn | One request-and-response cycle between Claude Code and a model. A single instruction from you usually costs several turns. |

## The problem, stated exactly

Claude Code sends `POST /v1/messages`. Cloudflare's AI Gateway has an endpoint that speaks that format, documented for Claude Code by Cloudflare itself, and it reaches Anthropic's models only.

Every other model in Cloudflare's catalogue — Kimi, GLM, Grok, DeepSeek, MiniMax — is listed as Chat Completions. Each model page in the catalogue states this in a field named `Request formats`.

| Model | Request formats as catalogued | Reachable by Claude Code unmodified |
| --- | --- | --- |
| anthropic/claude-opus-5 | Anthropic Messages | yes |
| minimax/m3 | Chat Completions, Anthropic Messages | see the measurement below |
| moonshotai/kimi-k3 | Chat Completions | no |
| xai/grok-4.5 | Chat Completions | no |
| deepseek/deepseek-v4-pro | Chat Completions | no |
| @cf/moonshotai/kimi-k2.7-code | Chat Completions | no |
| @cf/zai-org/glm-5.2 | Chat Completions | no |

```widget
{
  "type": "source",
  "id": "s6"
}
```

```widget
{
  "type": "source",
  "id": "s5"
}
```

So one piece is missing: a translator between the two formats. Nothing else about Claude Code needs to change.

## Before you start

Five prerequisites. Each one is a link and a check you can run.

1. **A Cloudflare account.** Sign up at [dash.cloudflare.com/sign-up](https://dash.cloudflare.com/sign-up). The free plan is enough to deploy the translator; model usage is paid per token.
2. **An AI Gateway with authentication turned ON.** Dashboard path and screenshots: [How to create a Cloudflare AI Gateway](/a/cloudflare-ai-gateway-setup). Authentication must be on, because Unified Billing refuses an unauthenticated gateway — proof of that refusal is further down.
3. **A Cloudflare API token** with `Workers AI: Read`, `Workers AI: Run` and `AI Gateway: Run` on your account. Create it at [dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) → Create Token → Create Custom Token, then add those three permission rows. Copy the token once; Cloudflare does not show it again.
4. **Your Cloudflare account ID.** Dashboard → any domain → the right-hand sidebar, or the 32-character hex string in your dashboard URL.
5. **Node.js 20 or newer.** Check with `node --version`. Install from [nodejs.org](https://nodejs.org). Wrangler, Cloudflare's deploy tool, runs through `npx` and needs no separate install.

## Route A: Claude models, on your Cloudflare bill, no code

If you only want Claude billed through Cloudflare, stop after this section. Cloudflare documents it and it needs no translator.

```bash
export ANTHROPIC_BASE_URL="https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/<GATEWAY_ID>/anthropic"
export ANTHROPIC_API_KEY="<CF_AIG_TOKEN>"
export ANTHROPIC_CUSTOM_HEADERS="cf-aig-authorization: Bearer <CF_AIG_TOKEN>"
claude
```

`<CF_AIG_TOKEN>` is a gateway token with Run permission. Cloudflare's own words: "The Anthropic endpoint exposes the same `/v1/messages` API that Claude Code expects."

```widget
{
  "type": "source",
  "id": "s3"
}
```

Check it worked, before starting a session:

```bash
curl -s -o /dev/null -w "%{http_code}\n" \
  -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
  -H "cf-aig-authorization: Bearer <CF_AIG_TOKEN>" \
  -H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-4-5","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
```

Expected output: `200`. A `403` means the gateway token lacks Run permission. A `401` means the `cf-aig-authorization` header is missing or wrong.

## Route B: any model, through one translator you deploy

Six commands. The translator is one file, MIT licensed, in a public repository.

```bash
git clone https://github.com/redacted/claude-code-cloudflare-gateway
cd claude-code-cloudflare-gateway
npx wrangler secret put CF_ACCOUNT_ID    # paste the 32-character account id
npx wrangler secret put CF_API_TOKEN     # paste the token from prerequisite 3
npx wrangler secret put SHIM_TOKEN       # paste any random string: openssl rand -base64 24
npx wrangler deploy
```

`wrangler deploy` prints the URL it deployed to, in the form `https://claude-code-cloudflare-gateway.<your-subdomain>.workers.dev`.

```widget
{
  "type": "source",
  "id": "s32"
}
```

Point Claude Code at it:

```bash
export ANTHROPIC_BASE_URL="https://claude-code-cloudflare-gateway.<your-subdomain>.workers.dev/<SHIM_TOKEN>"
export ANTHROPIC_AUTH_TOKEN="<SHIM_TOKEN>"
export ANTHROPIC_API_KEY=""
export ANTHROPIC_MODEL="kimi"
export ANTHROPIC_DEFAULT_OPUS_MODEL="kimi"
export ANTHROPIC_DEFAULT_SONNET_MODEL="kimi"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-flash"
export CLAUDE_CODE_SUBAGENT_MODEL="kimi"
export ENABLE_TOOL_SEARCH=true
export CLAUDE_CODE_ATTRIBUTION_HEADER=0
claude
```

Why each line exists:

- `ANTHROPIC_BASE_URL` — the address Claude Code sends every request to. The token is the last path segment because an existing `claude` login can override `ANTHROPIC_AUTH_TOKEN`; measured on the desktop app, which sent its own credential instead of the variable. A token in a URL can appear in logs, so the translator also accepts it in an `x-api-key` header if you prefer that.
- `ANTHROPIC_AUTH_TOKEN` — sent as `Authorization: Bearer`. `ANTHROPIC_API_KEY` is sent as `x-api-key` instead. Setting the wrong one produces a silent `401`.
- `ANTHROPIC_MODEL` and the three `_DEFAULT_` lines — Claude Code fills four model slots, not one. Leave any of them unset and that slot asks for a Claude model your gateway does not serve.
- `ANTHROPIC_DEFAULT_HAIKU_MODEL` — the background slot: session titles, summaries, quick classifications. `glm-flash` is `@cf/zai-org/glm-4.7-flash` at $0.06 per million input tokens, the cheapest model in the catalogue that still calls tools reliably.
- `ENABLE_TOOL_SEARCH=true` — stops every MCP tool definition being sent in every request. This single line changed a measured turn from 149,187 input tokens to 14,109. Explanation and measurements: [Why MCP tool schemas are most of your bill](/a/mcp-tool-search-cost).
- `CLAUDE_CODE_ATTRIBUTION_HEADER=0` — stops Claude Code prepending a value that changes on every request to the front of the system prompt, which prevents any upstream cache from ever matching. Detail below.

Verify before trusting it:

```bash
node tools/contract-test.mjs "https://<your-worker-host>/<SHIM_TOKEN>" kimi
```

21 checks, each printing PASS or FAIL: the response envelope, usage numbers, stop-reason mapping, the streaming event sequence in order, a streamed tool call whose arguments arrive incrementally and parse as JSON, a second turn that reads a tool result, token counting, the model list, and a wrong token being refused. All 21 pass on the reference deployment as of 2026-07-25.

## Which name gets you which model

Type the name on the left. The model on the right runs. Anything unrecognised resolves to the default rather than silently calling Anthropic.

| What you type | What runs | Context | Billed as |
| --- | --- | --- | --- |
| `kimi` | `@cf/moonshotai/kimi-k2.7-code` | 262,144 tokens | Workers AI |
| `k3` | `moonshotai/kimi-k3` | 1,048,576 tokens | Unified Billing |
| `glm` | `@cf/zai-org/glm-5.2` | 262,144 tokens | Workers AI |
| `glm-flash` | `@cf/zai-org/glm-4.7-flash` | 131,072 tokens | Workers AI |
| `grok` | `xai/grok-4.5` | per catalogue | Unified Billing |
| `minimax` | `minimax/m3` | per catalogue | Unified Billing |
| `opus5` / `sonnet5` | `anthropic/claude-opus-5` / `-sonnet-5` | per catalogue | Unified Billing |

Two names exist for one reason. The desktop client checks model names against a list of Anthropic-shaped names and refuses anything else before sending a request, so `claude-kimi-k2.7-code` and `claude-glm-5.2` also work and route to the same models. Any name containing `kimi`, `glm`, `grok` or `gpt` resolves to that family.

```widget
{
  "type": "source",
  "id": "s12"
}
```

One table in the translator generates both this mapping and the list served at `/v1/models`. The first version generated them separately, and `claude-kimi-k3` quietly ran K2.7 while `claude-glm-flash` ran GLM-5.2 at twenty times the input price. A published name that routes elsewhere is worse than no list at all.

## The money, with the arithmetic

Published rates, per million tokens, from Cloudflare's Workers AI pricing page:

| Model | Input | Cached input | Output |
| --- | --- | --- | --- |
| @cf/moonshotai/kimi-k2.7-code | $0.95 | $0.19 | $4.00 |
| @cf/zai-org/glm-5.2 | $1.40 | $0.26 | $4.40 |
| @cf/zai-org/glm-4.7-flash | $0.06 | none published | $0.40 |

```widget
{
  "type": "source",
  "id": "s15"
}
```

Four turns measured through one gateway, read from its own log rows:

| Configuration | Model | Input | Cached | Output | Cost | Latency |
| --- | --- | --- | --- | --- | --- | --- |
| MCP attached, no tool search | @cf/zai-org/glm-5.2 | 149,443 | 64 | 18 | $0.20922644 | 10.9 s |
| MCP attached, no tool search | @cf/moonshotai/kimi-k2.7-code | 149,187 | 64 | 19 | $0.02852109 | 4.6 s |
| MCP disabled entirely | @cf/moonshotai/kimi-k2.7-code | 21,928 | 13,312 | 45 | $0.01089448 | 2.8 s |
| MCP attached, tool search on | @cf/moonshotai/kimi-k2.7-code | 14,109 | 12,480 | 128 | $0.00443075 | 1.6 s |

```widget
{
  "type": "source",
  "id": "s7"
}
```

Read those four rows in order and three facts follow.

**Fact one: the tool definitions, not the conversation, are the bill.** Row four is the same MCP server as rows one and two, all its tools still reachable, at one tenth the input tokens — and cheaper than row three, which had no MCP server connected at all.

**Fact two: the model choice is a 7.3× multiplier on the same turn.** $0.20922644 against $0.02852109 for an identical 149k-token request.

**Fact three: one row does not reconcile, and that is stated rather than smoothed.** The GLM row multiplies out exactly — 149,379 uncached tokens at $1.40 per million is $0.20913, plus output, against a logged $0.20922644. The two small Kimi rows reconcile the same way. The second Kimi row does not: 149,123 uncached tokens at $0.95 per million should be $0.1417, and the log says $0.02852109, an effective $0.191 per million, which is the cached rate applied to input the same row reports as 64 tokens cached. Cloudflare's documentation calls the cost field "an estimation based on the number of tokens sent and received". Treat per-row cost as the platform's estimate and the token counts as the hard numbers. Every ratio above is computed from token counts.

**What that means per month.** At 60 turns of real agent work per day, 22 working days, with tool search on and the measured $0.00443 per turn: 60 × 22 × $0.00443 = **$5.85 per month**. The same 1,320 turns at the un-tuned GLM figure of $0.20922644 would be $276. A Claude Max subscription is $100 or $200 per month depending on tier. The comparison that matters is not model against model, it is tuned configuration against untuned: the same models, the same work, a 47× difference from two environment variables.

## The two settings that decide the bill

**Tool search.** Claude Code's default is to send every tool definition from every connected MCP server in every request. Captured on the same machine, same prompt, back to back:

```text
ENABLE_TOOL_SEARCH=false   tools=856   ['Agent','AskUserQuestion','Bash','CronCreate', ... 852 more]
ENABLE_TOOL_SEARCH=true    tools=9     ['Agent','AskUserQuestion','Bash','Edit','Read',
                                        'Skill','ToolSearch','Workflow','Write']
```

```widget
{
  "type": "source",
  "id": "s30"
}
```

The nine include a `ToolSearch` tool the model calls when it needs something not in front of it. Whether a non-Claude model actually uses it is the question that matters, because a deferred tool nobody looks for is a broken tool. Kimi K2.7 Code was asked for a tool it had never been shown; it called `ToolSearch`, found the tool named `TIME_NOW`, invoked it, and returned the payload:

```json
{"now":"2026-07-25T18:35:56-07:00","today":"2026-07-25","time":"18:35:56",
 "zone":"America/Los_Angeles","iso":"2026-07-25T18:35:56-07:00"}
```

```widget
{
  "type": "source",
  "id": "s31"
}
```

Anthropic's prompt-caching documentation states that MCP tool search is unavailable behind a custom `ANTHROPIC_BASE_URL` gateway, and Moonshot's own Claude Code guide instructs setting `ENABLE_TOOL_SEARCH=false`. On `claude-cli 2.1.165` against this gateway it worked and cut input tokens 10.6×. Both pages may be describing other versions or other endpoints. Re-check it on your version with the capture tool below rather than trusting any of the three claims, including this one.

**The attribution line.** Claude Code prepends a line of this shape to the system prompt:

```text
x-anthropic-billing-header: cc_version=2.1.177.01c; cc_entrypoint=sdk-cli; cch=<NONCE>;
```

The `cch=` value changes on every request. `api.anthropic.com` removes that line by position before the model sees it. Every other provider receives it, and because it sits in front of a 20,000-token prefix that would otherwise be identical between turns, no cache can match. Measured effect here: cached input per turn went from 64 tokens to between 12,480 and 14,976 once the line was gone. Set `CLAUDE_CODE_ATTRIBUTION_HEADER=0` on the client, and the translator removes the block server-side as well. Never merge or reorder the `system` array around it: the removal is positional, and a merged block beginning with that header takes the rest of your system prompt with it.

```widget
{
  "type": "source",
  "id": "s11"
}
```

## Why obedience is a reason on its own

Price is measurable and above. This is the other reason, stated plainly.

A model trained to be maximally helpful will improve on your instruction. It will add the thing you did not ask for, soften the thing you asked for bluntly, and summarise where you asked for the literal output. For a reader who needs an instruction executed exactly as written, that is not a small friction — it is the defect, and it is the trained behaviour, not a bug in a particular reply.

Open-weight models fine-tuned for coding — Kimi K2.7 Code, GLM-5.2 — are trained against benchmarks that reward completing the stated task. In practice they take a literal instruction more literally. That claim is not a benchmark result and is not presented as one; the benchmark numbers below say Claude leads on task completion. It is an operating observation, and the reason a cost-neutral switch can still be worth making: the cheaper model is also the one that does what the line says.

The structural point underneath it: an agent you can point at any model is an agent whose behaviour you can choose. An agent locked to one vendor's tuning is that vendor's judgement about what you meant. Everything above exists so that choice costs six commands instead of a rewrite.

## What people who did this report

Real accounts, positive and negative, quoted and linked. These are anecdotal reports from individuals, not measurements.

```widget
{
  "type": "source",
  "id": "s33"
}
```

```widget
{
  "type": "source",
  "id": "s34"
}
```

```widget
{
  "type": "source",
  "id": "s35"
}
```

```widget
{
  "type": "source",
  "id": "s36"
}
```

```widget
{
  "type": "source",
  "id": "s37"
}
```

## Every documented route, ranked

**1. The vendor's own Anthropic-format endpoint.** Shortest path, no proxy, no Cloudflare. You lose the single invoice and the gateway's logs.

```bash
# Kimi (Moonshot)
export ANTHROPIC_BASE_URL="https://api.moonshot.ai/anthropic"
export ANTHROPIC_AUTH_TOKEN="$MOONSHOT_API_KEY"
export ANTHROPIC_MODEL="kimi-k3[1m]"
export ENABLE_TOOL_SEARCH="false"   # required by Moonshot's own guide

# GLM (Z.ai)
export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"

# DeepSeek — claude-opus* maps to v4-pro, claude-sonnet*/haiku* to v4-flash
export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
```

```widget
{
  "type": "source",
  "id": "s19"
}
```

```widget
{
  "type": "source",
  "id": "s20"
}
```

```widget
{
  "type": "source",
  "id": "s21"
}
```

**2. OpenRouter.** One key, hundreds of models, officially supported, no proxy: "When you set `ANTHROPIC_BASE_URL` to `https://openrouter.ai/api`, Claude Code speaks its native protocol directly to OpenRouter. No local proxy server is required." Billed by OpenRouter, and also reachable as a first-class provider inside AI Gateway.

```widget
{
  "type": "source",
  "id": "s22"
}
```

**3. Cloudflare custom providers.** Register any HTTPS base URL as `custom-<slug>` and the gateway forwards your path to it, so a vendor's own Anthropic endpoint sits behind your gateway with its logging, caching and rate limits. The vendor still bills you; Unified Billing does not apply.

```widget
{
  "type": "source",
  "id": "s23"
}
```

**4. LiteLLM.** The most complete translator in existence and a proxy you must run and keep running. Correct on streaming, tool calls, thinking blocks with signatures, images and token counting. Its `cache_control` pass-through is limited to Claude and Bedrock targets, so cache breakpoints are dropped against a plain chat-completions backend anyway.

**5. claude-code-router.** 36,000 stars, actively developed, desktop app and CLI. Translation is delegated to a separate package; its npm `latest` tag has lagged its GitHub releases, so check the version you installed before trusting a number from its README.

**6. A local model, which needs no translator at all.** This inverted between late 2025 and early 2026: llama.cpp, vLLM, Ollama and LM Studio all serve `/v1/messages` natively now. llama.cpp merged it on 2025-11-28 and implements token counting; Ollama shipped it in v0.14.0 and explicitly does not support token counting, `tool_choice`, `metadata` or `cache_control`; LM Studio recommends at least 25,000 tokens of context "since Claude Code can be quite context-heavy." Any guide that tells you to run a proxy for a local model is out of date.

```widget
{
  "type": "source",
  "id": "s24"
}
```

```widget
{
  "type": "source",
  "id": "s25"
}
```

```widget
{
  "type": "source",
  "id": "s26"
}
```

## What breaks, and the exact fix

| Symptom | Cause | Fix |
| --- | --- | --- |
| `402` with `Gateway authentication is required to use unified billing` | Unified Billing is refused on a gateway with authentication off | Turn authentication on for that gateway and send `cf-aig-authorization` |
| Catalogue models 402 while `@cf/` models work | Same cause. Workers AI does not need Unified Billing | Same fix |
| `401` on every request, curl works | Wrong variable: `ANTHROPIC_AUTH_TOKEN` sends `Authorization: Bearer`, `ANTHROPIC_API_KEY` sends `x-api-key` | Match the variable to what your endpoint accepts; the translator here accepts either, plus the path token |
| Main thread works, subagents and session titles fail | Only `ANTHROPIC_MODEL` was set; the background and subagent slots still ask for a Claude model | Set `ANTHROPIC_DEFAULT_HAIKU_MODEL` and `CLAUDE_CODE_SUBAGENT_MODEL` |
| `400` naming `thinking` or `adaptive` | The client sends `thinking: {"type":"adaptive"}` to models that reject it | Use a translator that drops the field, or set `CLAUDE_CODE_DISABLE_THINKING=1` |
| `400 invalid thinking: only type=enabled is allowed for this model` on subagents only | The client omits `thinking` on subagent and structured-output calls; some endpoints require it | Known open defect against Moonshot's endpoint; the translator route avoids it by never forwarding the field |
| Model missing from `/model` | Gateway model discovery is off by default and drops every id not starting with `claude` or `anthropic` | Set `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`, or select with `claude --model <name>` |
| Turn ends with no visible output | Kimi and GLM spend the output budget on reasoning before answering; an empty assistant turn aborts the client | Raise `max_tokens`; the translator falls back to the reasoning text so the turn is never empty |
| Cost per turn far above the published rate | The attribution line is defeating the upstream cache | `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, and strip the block in the translator |
| Anthropic still receives usage metrics | Setting `ANTHROPIC_BASE_URL` alone does not turn metrics off | `DISABLE_TELEMETRY=1` |
| `403` with an HTML body, gateway logs show nothing | A web application firewall inspected the request body; prompts contain XML-like tags and code | Exempt `/v1/messages` from body inspection |

Two limits of the whole approach, in Anthropic's words and mine. Anthropic: it "doesn't endorse, maintain, or audit third-party gateway products, and doesn't support routing Claude Code to non-Claude models through any gateway." That is an unsupported configuration, not a prohibited one — no term forbids it — and it means the client changes when Anthropic changes it, and you catch up. Mine: images are forwarded, `thinking` is not, and prompt caching is whatever the upstream does with a prefix.

```widget
{
  "type": "source",
  "id": "s16"
}
```

```widget
{
  "type": "source",
  "id": "s17"
}
```

```widget
{
  "type": "source",
  "id": "s18"
}
```

## Do the cheaper models do the work

On identical independent harnesses, the open models sit one tier behind Claude, not two. SWE-bench Verified on mini-SWE-agent 2.0.0, one attempt each: Claude Opus 4.5 76.8, GLM-5 72.8, Claude Sonnet 4.5 71.4, Kimi K2.5 70.8. Terminal-Bench 2.1 with Claude Code as the harness: Fable 5 83.8, Opus 4.8 78.9, Sonnet 5 74.6, GLM-5.1 58.7, with no Kimi entry at all.

```widget
{
  "type": "source",
  "id": "s27"
}
```

```widget
{
  "type": "source",
  "id": "s28"
}
```

Every vendor-published score that has an independent counterpart is higher than the independent one. GLM-4.6: 68.2 on Z.ai's own scaffold, 55.4 on mini-SWE-agent. Kimi K2.5: 76.8 in-house, 70.8 independent. Claude Opus 4.6: 80.4 in-house over 25 trials, 75.6 independent. The direction is uniform, so read the scaffold before the score.

```widget
{
  "type": "source",
  "id": "s29"
}
```

Two more facts before anyone builds an argument on a leaderboard: the official SWE-bench Verified board's newest submission is from February 2026, and Anthropic stopped publishing SWE-bench Verified in text from Opus 4.7 onward — Opus 5 publishes neither it nor Terminal-Bench.

## Verify every measurement on this page yourself

Nothing here needs to be taken on trust. Two tools, both in the repository.

**The wire capture.** A local server that speaks the Anthropic Messages format, logs exactly what the client sent, and answers with a valid response. This is where every request-shape number on this page came from.

```bash
node tools/capture-gateway.mjs        # listens on :8787, appends capture.jsonl
ANTHROPIC_BASE_URL=http://localhost:8787 ANTHROPIC_AUTH_TOKEN=x claude -p "say ok"
```

What one capture on `claude-cli 2.1.165` recorded, 2026-07-25:

```text
HEAD /                          user-agent: Bun/1.3.14
POST /v1/messages?beta=true     model=<ANTHROPIC_MODEL>   max_tokens=32000   tools=861
POST /v1/messages?beta=true     model=<HAIKU_SLOT>        max_tokens=1024    tools=0
anthropic-version: 2023-06-01
anthropic-beta: claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,
  context-management-2025-06-27,prompt-caching-scope-2026-01-05,
  mid-conversation-system-2026-04-07,effort-2025-11-24,extended-cache-ttl-2025-04-11
system: [ 92 chars, 62 chars, 5681 chars ]   cache_control: [ none, ephemeral, ephemeral ]
```

```widget
{
  "type": "source",
  "id": "s1"
}
```

Four constraints for anyone writing a translator follow from that capture, and the reference implementation obeys all four: match on the path because the query string is present; serve both model slots; keep the `system` array in its original order; and expect the tool list, not the prompt, to dominate the payload.

**The contract test.** 21 checks against a live deployment, listed earlier in Route B. Run it after every change.

```widget
{
  "type": "source",
  "id": "s2"
}
```

## The complete requirement list for a translator

Seven behaviours. Each one is a real defect in at least one published shim, so each is checked by the contract test.

1. **Stream.** A gateway that buffers whole responses stalls the client.
2. **Emit tool arguments incrementally** — `content_block_start`, then `input_json_delta` chunks — not one blob at the end.
3. **Order tool results correctly.** Anthropic puts `tool_result` blocks inside a user turn; Chat Completions wants `role:"tool"` messages immediately after the assistant turn that called them.
4. **Map stop reasons**: `stop` to `end_turn`, `length` to `max_tokens`, `tool_calls` to `tool_use`. Nobody can produce `stop_sequence` faithfully, because Chat Completions returns `stop` for both cases with no discriminator.
5. **Never return an empty turn.** Fall back to the reasoning text when the output budget was spent before any answer.
6. **Answer `/v1/messages/count_tokens`.** It is optional, and without it the client estimates locally; an estimate beats a 404.
7. **Remove the attribution block without reordering the `system` array.**

Four Cloudflare Workers shims exist and each fails at least one of these: `luohy15/y-router` is archived and drops `max_tokens`; `glidea/claude-worker-proxy` is active but sends tool arguments as one blob, has no token-count route and drops images; `tingxifa/claude_proxy` types `system` as a string, which is not the shape the client sends; `mrdear/cloudflare-ai-proxy` ignores images by design.

```widget
{
  "type": "source",
  "id": "s8"
}
```

```widget
{
  "type": "source",
  "id": "s9"
}
```

```widget
{
  "type": "source",
  "id": "s10"
}
```

## What this costs to keep running

The translator is a Cloudflare Worker. On the free plan that is 100,000 requests per day at no charge; a heavy day of agent work is a few hundred. The gateway itself is free; its logs are retained per your plan. Model tokens are the only real cost, at the rates above. Unified Billing adds 5% on purchased credits and passes provider pricing through with no markup, and Workers AI models are billed as Workers AI rather than through it — the arithmetic of that split is in [Cloudflare Unified Billing](/a/cloudflare-unified-billing).

```widget
{
  "type": "source",
  "id": "s13"
}
```

One security fact to hold onto: Cloudflare states that AI Gateway token permissions "cannot be restricted to a single gateway", so a token with Run reaches every gateway on the account, including any holding stored provider keys. Rate-limit the gateway you use for this — 120 requests per 60 seconds is enough for one operator and caps the damage if a token leaks.

```widget
{
  "type": "source",
  "id": "s14"
}
```

## Facts

```json
{
  "converted_from": "markdown",
  "original_updated": "2026-07-26T05:37:26.060Z",
  "register": "essay",
  "category": null
}
```

## Sources

1. [Wire capture: what claude-cli 2.1.165 sends to an arbitrary ANTHROPIC_BASE_URL](https://miscsubjects.com/api/articles/claude-code-on-cloudflare-ai-gateway) — “POST /v1/messages?beta=true | model=<ANTHROPIC_MODEL> max_tokens=32000 tools=861 | system=[92, 62, 5681] cache_control=[none, ephemeral, ephemeral]” (sha256 bff72ea634a5bcec)
2. [Claude Code gateway protocol reference](https://code.claude.com/docs/en/llm-gateway-protocol) — “Forward the header verbatim; don't allowlist individual values, because the set changes with Claude Code releases.” (sha256 69c53b865a6a5ffe)
3. [Cloudflare AI Gateway — Claude Code integration](https://developers.cloudflare.com/ai-gateway/integrations/coding-agents/claude-code/) — “The Anthropic endpoint exposes the same /v1/messages API that Claude Code expects.” (sha256 e3daad317d75539b)
4. [Cloudflare AI Gateway — Anthropic provider](https://developers.cloudflare.com/ai-gateway/usage/providers/anthropic/) — “https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/anthropic” (sha256 4df337fcaada609d)
5. [Cloudflare AI Gateway — REST API](https://developers.cloudflare.com/ai-gateway/usage/rest-api/) — “The /ai/v1/messages endpoint strictly uses Anthropic's API schema and supports routing to Anthropic and other third-party models. Workers AI models (@cf/) do not support this schema.” (sha256 1a58894abeedd5c8)
6. [Cloudflare model catalogue — Request formats field](https://developers.cloudflare.com/ai/models/moonshotai/kimi-k3/) — “Request formats: Chat Completions” (sha256 1d7ce5eddb69160d)
7. [Gateway log rows: measured cost, cached input and the Unified Billing 402](https://miscsubjects.com/api/articles/claude-code-on-cloudflare-ai-gateway) — “{"errors":[{"message":"Gateway authentication is required to use unified billing. Enable authentication on your gateway or provide your own API key (BYOK).","code":2021}]}” (sha256 d14c4cff5e388a2d)
8. [y-router — archived Cloudflare Worker Anthropic-to-OpenAI proxy](https://github.com/luohy15/y-router) — “docs: Archive project in favor of official OpenRouter integration” (sha256 b08a41e2acb884e9)
9. [claude-worker-proxy — the one active Cloudflare Worker shim](https://github.com/glidea/claude-worker-proxy) — “Anthropic Messages to OpenAI-compatible proxy running on Cloudflare Workers” (sha256 029d019bac6432cc)
10. [LiteLLM #34522 — Kimi K2 tool calls silently stop working after tool_use id normalization](https://github.com/BerriAI/litellm/issues/34522) — “Kimi K2 tool calls silently stop working on v1.91.3+ due to tool_use id normalization” (sha256 acf228b6d9256819)
11. [claude-code #68900 — per-request billing-header nonce breaks prompt caching on third-party providers](https://github.com/anthropics/claude-code/issues/68900) — “x-anthropic-billing-header: cc_version=2.1.177.01c; cc_entrypoint=sdk-cli; cch=<NONCE>;” (sha256 dffc397bd738998b)
12. [claude-code #56990 — desktop build rejects non-Anthropic model names](https://github.com/anthropics/claude-code/issues/56990) — “expected a gateway model route referencing an Anthropic model (e.g. claude-sonnet-4-5, anthropic/claude-*). Name routes to match the underlying model.” (sha256 49631344abeb04cb)
13. [Cloudflare AI Gateway — Unified Billing](https://developers.cloudflare.com/ai-gateway/features/unified-billing/) — “A 5% fee is applied to all credits purchased through Unified Billing. For example, a $100 credit purchase will result in a $105 charge. Inference pricing from providers is passed through with no markup.” (sha256 ee18d9dacfc5af5b)
14. [Cloudflare AI Gateway — authenticated gateway and token scope](https://developers.cloudflare.com/ai-gateway/configuration/authentication/) — “The AI Gateway Read, Run, and Edit permissions cannot be restricted to a single gateway — unlike R2, which supports per-bucket scoping. Any token with AI Gateway Run can send requests through every gateway in the account, including any configured with stored provider keys through Bring Your Own Keys (BYOK), consuming those credentials.” (sha256 5b63f3f7378a5828)
15. [Workers AI pricing](https://developers.cloudflare.com/workers-ai/platform/pricing/) — “$0.011 per 1,000 Neurons” (sha256 68a746d1d8d88018)
16. [Claude Code — LLM gateway configuration](https://code.claude.com/docs/en/llm-gateway) — “Anthropic doesn't endorse, maintain, or audit third-party gateway products, and doesn't support routing Claude Code to non-Claude models through any gateway.” (sha256 cec08f2fadd1e703)
17. [claude-code #68551 — adaptive thinking sent to custom base-URL models](https://github.com/anthropics/claude-code/issues/68551) — “Claude Code sends thinking: {type: adaptive} to custom ANTHROPIC_BASE_URL models — gateways 400 / return empty / hang” (sha256 4448ccf1cfcb7cf2)
18. [claude-code #69379 — subagents 400 against a third-party Anthropic endpoint](https://github.com/anthropics/claude-code/issues/69379) — “API Error: 400 invalid thinking: only type=enabled is allowed for this model” (sha256 fbb362f43da1918d)
19. [Moonshot — using Kimi in Claude Code](https://platform.kimi.ai/docs/guide/claude-code-kimi) — “export ANTHROPIC_BASE_URL="https://api.moonshot.ai/anthropic"” (sha256 9e6ae11bbf79bcc8)
20. [Z.ai — GLM in Claude Code](https://docs.z.ai/devpack/tool/claude) — “"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic"” (sha256 b48b3db85178471c)
21. [DeepSeek — Anthropic API format](https://api-docs.deepseek.com/guides/anthropic_api) — “To meet the demand for using the Anthropic API ecosystem, our API has added support for the Anthropic API format, with the base_url being https://api.deepseek.com/anthropic” (sha256 8908fc06566336a2)
22. [OpenRouter — Claude Code integration](https://openrouter.ai/docs/guides/guides/claude-code-integration) — “When you set ANTHROPIC_BASE_URL to https://openrouter.ai/api, Claude Code speaks its native protocol directly to OpenRouter. No local proxy server is required.” (sha256 f733ddea932c10c6)
23. [Cloudflare AI Gateway — custom providers](https://developers.cloudflare.com/ai-gateway/configuration/custom-providers/) — “Everything after custom-{slug}/ in your request URL is appended directly to the base_url to form the final upstream URL.” (sha256 46e40de5a1a9f598)
24. [Ollama — Anthropic API compatibility](https://docs.ollama.com/api/anthropic-compatibility) — “API key is accepted but not validated.” (sha256 046411c2991f7869)
25. [llama.cpp #17570 — Anthropic Messages API in llama-server](https://github.com/ggml-org/llama.cpp/pull/17570) — “While no strong claims of compatibility with the Anthropic API spec are made, in our experience it suffices to support many apps.” (sha256 98bb742a6f1c522a)
26. [vLLM — Claude Code integration](https://docs.vllm.ai/en/latest/serving/integrations/claude_code/) — “Claude Code requires a model with strong tool calling capabilities” (sha256 ce89a5468e03eea1)
27. [SWE-bench Verified leaderboard](https://www.swebench.com/) — “mini-SWE-agent 2.0.0 — glm-5 (high) 72.8” (sha256 7a389e27c0c9eda0)
28. [Terminal-Bench 2.1 leaderboard](https://www.tbench.ai/leaderboard/terminal-bench/2.1) — “Claude Code + Fable 5 — 83.8 ± 1.2” (sha256 49567259002773e6)
29. [Kimi K2.6 model card — vendor-reported scores](https://huggingface.co/moonshotai/Kimi-K2.6) — “SWE-bench Verified 80.2” (sha256 38cac097b1d81b3b)
30. [Tool-search capture: 856 tool definitions become 9](https://miscsubjects.com/api/articles/claude-code-on-cloudflare-ai-gateway) — “ENABLE_TOOL_SEARCH=false  tools=856   |   ENABLE_TOOL_SEARCH=true  tools=9  ['Agent','AskUserQuestion','Bash','Edit','Read','Skill','ToolSearch','Workflow','Write']” (sha256 10bd4b7e7096ca8b)
31. [Gateway log rows with tool search on, and a non-Claude model using it](https://miscsubjects.com/api/articles/claude-code-on-cloudflare-ai-gateway) — “{"now":"2026-07-25T18:35:56-07:00","today":"2026-07-25","time":"18:35:56","zone":"America/Los_Angeles","iso":"2026-07-25T18:35:56-07:00"}” (sha256 07b1c88f15e1225e)
32. [claude-code-cloudflare-gateway — the Worker from this article](https://github.com/redacted/claude-code-cloudflare-gateway) — “Run Claude Code against Kimi, GLM, Grok or Claude through your own Cloudflare AI Gateway. One Worker, one bill, no provider keys.” (sha256 0c6c1e2f6a14620f)
33. [I gave Claude Code a $0.02/call coworker and stopped hitting Pro limits](https://old.reddit.com/r/ClaudeAI/comments/1t1o43w/i_gave_claude_code_a_002call_coworker_and_stopped/) — “Was hitting my weekly Pro limit by Wednesday every single week. Tried compact, Sonnet for simple tasks, tighter prompts — nothing worked.” (sha256 c5489cd25d5303e2)
34. [Qwen 3.6 is actually useful for vibe-coding, and way cheaper than Claude](https://old.reddit.com/r/LocalLLaMA/comments/1st3m8y/qwen_36_is_actually_useful_for_vibecoding_and_way/) — “Launched claude code, pointed it at my running Qwen, and, well, it vibe codes perfectly fine.” (sha256 48b10b57052006ad)
35. [From Kimi K3 to Claude Opus 5](https://old.reddit.com/r/ClaudeAI/comments/1v6224v/from_kimi_k3_to_claude_opus_5/) — “From Kimi K3 to Claude Opus 5” (sha256 cc70abeb32647f45)
36. [Per-model settings file for switching Claude Code to Kimi](https://x.com/chongdashu/status/2016156608875602204) — “Want to use Kimi 2.5 in Claude Code? Step 1: create a file .claude/settings/kimi.json” (sha256 b3f489e73de651a5)
37. [cc-compatible-models — community catalogue of Claude Code backends](https://github.com/Alorse/cc-compatible-models) — “Complete guide for using alternative AI models with Claude Code — including DeepSeek, Qwen, MiniMax, Kimi, GLM, MiMo, StepFun, and more. Pricing, configs, and coding plans.” (sha256 e1d05c3fbbef0201)
