Running Claude Code on DeepSeek: Everything Works, But the Cost Readout Lies by 38x
DeepSeek's API speaks the Anthropic format at https://api.deepseek.com/anthropic. Three environment variables and Claude Code talks to DeepSeek instead of Anthropic.
Plenty of posts tell you those three variables. Almost none tell you what degrades. That is the only question worth asking — an agentic coding tool is not a chat box, it lives or dies on tool calls, multi-turn state, and whether the numbers on your screen mean anything.
So I ran it. Claude Code 2.1.270, macOS, in an isolated config directory that never touches my real setup. The Claude Code runs below all target deepseek-v4-pro; deepseek-flash appears in the raw-endpoint mapping tests and as the Haiku slot.
Short version: the functionality is fine, the cost readout is not. Claude Code told me I had spent $1.71. DeepSeek charged me ¥0.32.
Background: why anyone wants this
Two reasons, and they pull in different directions.
The boring one is price. DeepSeek V4 Pro lists at $1.32 / 1M input tokens at peak and $0.66 off-peak, against Claude's per-million rates in a different bracket entirely. For a tool that re-sends a large system prompt on every single turn, that ratio compounds fast.
The interesting one is that Claude Code is, at this point, the most capable agentic harness in wide use — and it is the part you cannot easily rebuild. Being able to keep the harness and swap the engine underneath is genuinely useful, if the swap is clean.
Whether it is clean is an empirical question, so here is the measurement.
What I tested, and what I deliberately did not
Tested: connectivity, the model-name mapping, the local tool matrix, subagent spawning, prompt caching across turns, and real billed cost against the actual account balance.
Not tested (and why):
- MCP servers. DeepSeek's compatibility table lists the API's
mcp_serversfield as Ignored, but that field is for Anthropic's server-side MCP. Claude Code's MCP is local stdio and never touches it. Testing the API field would prove nothing about the tool you actually use, and properly testing local MCP needs its own article. - Long-context behaviour at 1M. DeepSeek advertises a 1M context window. Claude Code reports
contextWindow: 200000for the model. Resolving which one wins needs a dedicated filling experiment; I am not going to guess from a metadata field. - Vision. The pricing page states v4-pro does not support vision while flash does. I did not exercise image input.
Everything below was run on one machine on 2026-09-20, with raw logs kept.
Setup
Three variables do the work:
export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
export ANTHROPIC_AUTH_TOKEN="$DEEPSEEK_API_KEY" # not ANTHROPIC_API_KEY
unset ANTHROPIC_API_KEY
export ANTHROPIC_MODEL="deepseek-v4-pro"
Use ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY — with both set Claude Code prompts about the conflict. I also ran everything under a throwaway CLAUDE_CONFIG_DIR and a scratch working directory, so nothing here can reach a real project:
export CLAUDE_CONFIG_DIR="$PWD/cc-config"
Ground truth first: what the endpoint actually does
Before touching Claude Code I hit the raw endpoint, because if the mapping layer surprises you, everything above it is guesswork.
Model mapping is prefix-based — and the docs are wrong about the fallback
| Model name sent | Model returned | Latency |
|---|---|---|
claude-opus-5 |
deepseek-v4-pro |
3.20s |
claude-sonnet-5 |
deepseek-flash |
1.03s |
claude-haiku-4-5-20251001 |
deepseek-flash |
1.32s |
deepseek-v4-pro |
deepseek-v4-pro |
1.97s |
deepseek-flash |
deepseek-flash |
1.44s |
totally-made-up-model |
HTTP 400 | 0.38s |
The claude-opus* → v4-pro and claude-sonnet*/claude-haiku* → flash mapping matches the documentation.
The last row does not. The docs say:
When you pass an unsupported model name to DeepSeek's Anthropic API, the API backend will automatically map it to the
deepseek-flashmodel.
It does not. It returns a 400:
{"error":{"message":"The supported API model names are deepseek-flash, deepseek-v4-pro, but you passed totally-made-up-model.","type":"invalid_request_error"}}
So the real rule is: claude-* prefixes map, everything else must be an exact DeepSeek model name or you get a 400. If you are scripting model selection, do not rely on the documented fallback.

v4-pro returns thinking blocks by default
Ask for max_tokens: 64 and the reply looks empty. It isn't — the budget went to reasoning:
"content": [
{ "type": "thinking", "thinking": "We need answer user asks \"Say OK.\" ...", "signature": "4b525c96-..." },
{ "type": "text", "text": "OK" }
]
Thinking mode is on by default for both models per DeepSeek's pricing page. If you are writing a thin client against this endpoint, budget max_tokens for reasoning or you will ship a bug where short answers come back blank. (Claude Code handles this correctly; this bites custom integrations.)
/v1/models 404s, harmlessly
GET /v1/models?limit=1000 -> HTTP 404
GET /v1/models -> HTTP 404
Claude Code probes this on startup and carries on regardless. Cosmetic.
The tool matrix: everything works
This is the part that could have gone badly and didn't. Each row is one claude -p run in the scratch directory, and I verified the side effects on disk, not just the model's claim of success.
| Test | Turns | Input | Cache read | Output | Wall | Result | Side effect verified |
|---|---|---|---|---|---|---|---|
| Read a file | 2 | 34,186 | 34,176 | 107 | 8.7s | hello from lab |
— |
| Write a file | 2 | 34,265 | 34,176 | 131 | 9.0s | DONE |
written.txt = WRITTEN_OK ✅ |
| Run Bash | 2 | 34,171 | 34,176 | 93 | 6.8s | BASH_OK |
— |
| Glob | 2 | 34,284 | 34,432 | 454 | 10.3s | both filenames | — |
| Edit in place | 3 | 34,474 | 68,480 | 272 | 8.7s | EDITED |
probe.txt hello→goodbye ✅ |
Subagents work too. Asking for the Agent tool produced a real spawn, and tracing the stream shows the actual call chain:
t9b tools actually invoked: ['Agent', 'Bash']

Prompt caching works. The first (cold) call shows cache_read_input_tokens: 0; every subsequent call reads ~34K from cache. I initially misread that cold-start zero as "caching is broken" — it isn't, and if you only look at your first request you will reach the same wrong conclusion.
Note the constant in that table: ~34,000 input tokens on every turn, before you type anything. That is Claude Code's system prompt plus tool definitions. It is why the cache-hit price matters more than the headline input price.
The headline: the cost readout is off by 38x
Claude Code prints a cost. Against DeepSeek, that number is fiction.
I ran ten identical turns and measured the actual account balance before and after, rather than deriving anything from a price list:
| Value | |
|---|---|
| Claude Code reported (sum of 10 runs) | $1.7097 |
| DeepSeek balance before | ¥37.58 |
| DeepSeek balance after | ¥37.26 |
| Real spend | ¥0.32 ≈ $0.045 |
| Over-report | 38x |
Where does the fake number come from? One cold run reported $0.171215 on 34,098 input + 29 output tokens. Solving for the rate:
34,098 × $5/1M + 29 × $25/1M = $0.171215 ← exact match
Claude Code is billing DeepSeek tokens at $5 / $25 per million — Claude Sonnet's rate card. It even flags its own uncertainty in the JSON ("costBasis": "unknown") and prints a confident dollar figure anyway.
Two practical consequences:
/costand the JSONtotal_cost_usdare unusable here. Anything you build on them — budget alarms, per-task accounting, "is this worth it" decisions — will be wrong by more than an order of magnitude.- Use DeepSeek's own
GET /user/balanceif you need real numbers. Caveat: it reports to two decimal places in CNY, so single calls are below its resolution — batch until the delta is measurable. My conversion uses ¥7.1/$; the 38x ratio is insensitive to reasonable rate choices.

One honest limit: balance-delta at ¥0.01 precision over ten runs is a coarse instrument. The direction and order of magnitude are solid; treat "38x" as ~40x, not as four significant figures.
The failure that is not DeepSeek's fault
WebFetch failed on every attempt:
is_error=True Unable to verify if domain example.com is safe to fetch.
This may be due to network restrictions or enterprise security policies blocking claude.ai.
The model then quietly fell back to Bash + curl and got the right answer anyway — which is exactly the kind of silent degradation worth catching.
My first hypothesis was that CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 — which DeepSeek's own integration guide recommends — was blocking the domain-safety check. That hypothesis was wrong. Controlled test:
| Group | DISABLE_NONESSENTIAL_TRAFFIC |
Proxy | WebFetch |
|---|---|---|---|
| A | 1 |
none | ❌ fails |
| B | unset | none | ❌ fails |
| C | 1 |
on | ✅ The page title is "Example Domain" |
The variable is irrelevant. The domain-safety check calls back to Anthropic's infrastructure, and claude.ai is unreachable from mainland China without a proxy. I hit the identical error earlier the same day from a session running on genuine Anthropic credentials.
So: if you are in China and WebFetch breaks after switching to DeepSeek, do not blame DeepSeek. It was broken before you switched. Set a proxy.

I am spelling this out because it is the single easiest false finding to publish here, and I nearly did.
Cosmetic noise you can ignore
Every call writes this to stderr:
[claude-code:unrecognized_model] {"model":"deepseek-v4-pro","query_source":"sdk"}
Claude Code does not know the model name. It proceeds correctly. If you are parsing stderr in a script, filter it.
Verdict
Use it for: bulk mechanical work where tool calls dominate and per-token cost matters — refactors, test scaffolding, log spelunking, anything you would feel bad about spending Opus tokens on. The tool layer is genuinely solid; nothing in my matrix silently produced a wrong side effect.
Do not use it for: anything where you need the cost display to be true, or where you are relying on Claude Code's reported context window matching the model's real one. Both are wrong right now.
The one thing to change immediately if you set this up: stop reading /cost. Put DeepSeek's balance endpoint in a two-line script instead.
Pitfalls, condensed
- The docs' unknown-model fallback does not exist — non-
claude-*names that aren't exact DeepSeek model IDs return HTTP 400, not a silent remap to flash. - Thinking blocks eat
max_tokens— a small budget yields an apparently empty reply. Affects custom clients, not Claude Code. - Cold-start cache reads are zero — do not conclude caching is broken from your first request.
total_cost_usdis Sonnet pricing applied to DeepSeek tokens — ~38x high, measured against the real invoice.- WebFetch failures in China are a proxy problem, not a DeepSeek problem — verify with a control group before writing it up as a compatibility gap.
/v1/models404 andunrecognized_modelon stderr are both harmless — do not chase them.
Tested 2026-09-20 on Claude Code 2.1.270, macOS/Apple Silicon, against deepseek-v4-pro (DeepSeek-V4-Pro-0813) and deepseek-flash (DeepSeek-V4.1-Flash). Pricing figures read from DeepSeek's official pricing page on the same day; spend measured via GET /user/balance before and after.