Claude Code and Anthropic SDKs
Run Claude Code and Anthropic SDK clients on your own models through the gateway's Anthropic Messages API.
5 min readUpdated for v2.4.0Edit on GitHub
ANTHROPIC_BASE_URL and ANTHROPIC_API_KEYPOST /v1/messages, POST /v1/messages/count_tokensmessagesThe gateway serves the Messages API itself: each request is translated to a chat request, runs through the same admission, budgets, boons and billing as /v1/chat/completions, and the reply is translated back. Your registered models answer; no Anthropic service is involved.
Set up Claude Code
- 1
Point it at the gateway
Set
ANTHROPIC_BASE_URLto the data-plane origin without a path (the client adds/v1/messages), and a gateway key as the credential:export ANTHROPIC_BASE_URL=http://localhost:8088 export ANTHROPIC_API_KEY=sk_... claudeANTHROPIC_API_KEYis sent asx-api-keyandANTHROPIC_AUTH_TOKENasAuthorization: Bearer; the gateway accepts both, and an identity token works in either. To keep the settings, put them in theenvblock of~/.claude/settings.json, not in a project's.claude/settings.json, which is usually committed. - 2
Pick the model that answers
Claude Code sends Claude model ids that your gateway does not serve. Map them with one of the options in the next section.
- 3
Check it
curl -s "$ANTHROPIC_BASE_URL/v1/messages" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{"model": "qwen3-coder", "max_tokens": 64, "messages": [{"role": "user", "content": "ping"}]}'A
messageobject back means the key, the model and its backend all work. Anot_found_errormeans the model name did not resolve.
The Anthropic SDKs take the same two values:
from anthropic import Anthropic
client = Anthropic(base_url="http://localhost:8088", api_key="sk_...")
message = client.messages.create(
model="qwen3-coder",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)
Map Claude model ids to your models
Use any of these; they combine.
- Pin the model on the client. Set
ANTHROPIC_MODEL(orclaude --model) for the main session, andANTHROPIC_DEFAULT_SONNET_MODEL,ANTHROPIC_DEFAULT_OPUS_MODEL,ANTHROPIC_DEFAULT_HAIKU_MODELandCLAUDE_CODE_SUBAGENT_MODELfor the rest. The value is any gateway model name, alias orauto. - Add Claude ids as aliases. Add the ids the client sends, such as
claude-sonnet-4-6, to the aliases of the model that should answer. Aliases match exactly, so a client release with new ids needs new aliases. - Set a default for Anthropic clients. In Settings › Routing, choose a model under Default for Anthropic clients, and save. Any name that does not resolve on
/v1/messagesis then served by that model; it can beauto. Over the API it ismessages_default_modelonPUT /api/v1/settings/auto-router, and an empty string clears it.
For each request the gateway uses auto as sent, then a name or alias it knows, then the default. With none of these it returns 404 not_found_error with model: <name>. Routing, pools, budgets and usage follow the model that served the request, while the response's model field echoes the name the client sent. The default does not stand in for a model that exists but is disabled; that returns 403.
Claude Code on your gateway is only as good as the model you route it to. Use a model with function calling on (supports_function_calling), a large context window, and an accurate context_window setting, which the gateway uses to fit max_tokens.
What is translated
| Anthropic | Chat completions |
|---|---|
system, and system-role entries inside messages | One leading system message, in order |
Text and image blocks (base64 or URL) | Text and image_url parts |
tool_use and tool_result blocks | tool_calls and tool messages |
tools, tool_choice | Function tools; auto, required, a named function or none |
disable_parallel_tool_use: true | parallel_tool_calls: false |
stop_sequences, metadata.user_id | stop, user |
stream: true | A chat stream, re-emitted as Anthropic events |
Other top-level fields (temperature, top_p, …) | Passed through |
Replies come back as a message object with thinking (from the model's reasoning output), text and tool_use blocks, and stop_reason of end_turn, max_tokens or tool_use. Streams send ping every 15 seconds while the backend is quiet.
Usage follows Anthropic's fields. input_tokens is the input the backend did not serve from its prefix cache, cache_read_input_tokens the part it did, and cache_creation_input_tokens is always 0, since a prefix cache has no write charge. A stream's message_start is sent before usage is known, so its counts are 0; the final message_delta carries them.
Token counting
POST /v1/messages/count_tokens takes the same body (max_tokens optional) and returns {"input_tokens": 1834}, the gateway tokenizer's estimate of the message text. It takes no slot, reserves no budget and writes no usage row.
Not supported
- Prompt cache control.
cache_controlmarkers are dropped. Caching is up to the backend's own prefix cache, and usage reports what it served. - Extended thinking budgets.
thinkingandoutput_config.effortare read as a reasoning effort level (disabledisnone; a budget is bucketed intolow,mediumorhigh) and reach the model only through its effort map or a model family. The budget itself is not passed on. Reasoning a model produces still comes back asthinkingblocks. - Images inside tool results. Only the text of a
tool_resultreaches the model. - Server-side tools. Web search, code execution, computer use,
container,mcp_serversand the batch API. The model's own boons, including the gateway tool loop, still run. - Which stop sequence fired.
stop_sequenceis alwaysnull. - A per-tenant default. The default for Anthropic clients is one gateway-wide setting.
Fitting max_tokens and "prompt is too long"
Anthropic clients size max_tokens for a Claude model, which can be more than a smaller model holds. Before sending, the gateway estimates the prompt (message text, plus about 3 characters per token for the tools array, plus 1500 tokens per image) and lowers max_tokens to what fits in the model's context_window, less a margin of 256 tokens or one eighth of the estimate, whichever is larger. For auto it uses the largest window among healthy chat models. With a context_window of 0 it sends max_tokens unchanged.
When the prompt alone does not fit, the request fails with 400 prompt is too long: <n> tokens > <window> maximum. That is Anthropic's wording, which Claude Code uses to start compacting the conversation. A backend's own context-length 400 is reworded the same way.
Request rules and errors
model, a positive max_tokens and a non-empty messages array are required. Roles other than user, assistant and system, and block types other than text, image, tool_use, tool_result, thinking and redacted_thinking, return 400. thinking blocks in the history are dropped. The anthropic-beta header and beta query parameter are removed before the request goes on. The body limit is 32 MiB.
Errors use the Anthropic envelope, with the pipeline's status and message, and keep Retry-After and x-obleth-request-id:
| Status | error.type |
|---|---|
| 400 | invalid_request_error |
| 401 | authentication_error |
| 403 | permission_error |
| 404 | not_found_error |
| 413 | request_too_large |
| 429 | rate_limit_error |
| 502, 503, 504 | overloaded_error (an admission timeout is a 503 with Retry-After: 5) |
| Other | api_error |
