Messages (Anthropic-compatible)
POST /v1/messages — Anthropic Messages API, with complete examples by scenario
Compatible with the Anthropic Messages API. Underneath it is the same routing pipeline as Chat Completions; only the request/response format is adapted to Anthropic style.
This page is organized by scenario: start with the scenario overview, then go to the section for request, response and caveats. Every example carries an evidence label. Tested means it was called for real with a live test account (2026-10-19); response fields come from the real reply, with id, reasoning text and similar values replaced by placeholders. Per official docs (not tested) means it was written from the Anthropic documentation and we did not verify it with a real call.
Authentication difference (important): the platform's /messages uses Authorization: Bearer $MODELSITE_API_KEY, not the official Anthropic x-api-key. With the official anthropic SDK pass auth_token (sent as Bearer) and do not pass api_key (it sends x-api-key and gets 401). The official SDK adds the anthropic-version header automatically; the platform neither enforces it nor forwards it upstream, but when writing raw requests keep sending it for compatibility. The anthropic-beta header is passed through.
Scenario overview
Scenarios follow Anthropic's Messages, Streaming, Tool use, Vision, Extended thinking, Prompt caching and Structured outputs docs.
| Scenario | Offered officially | Our status | Example |
|---|---|---|---|
Basic chat (single turn, system) | Yes | Supported | Basic chat |
| Multi-turn (you send the history) | Yes | Supported | Multi-turn |
| Streaming | Yes | Supported | Streaming |
Tool use (tool_use / tool_result, tool_choice) | Yes | Supported: auto / any / tool / none all work; disable_parallel_tool_use: true becomes parallel_tool_calls: false | Tool use |
Extended thinking (thinking) | Yes | Supported (thinking blocks carry no signature) | Thinking |
Image input (image blocks) | Yes | Partial: needs a vision model; base64 tested, url source not tested, file_id not supported | Image input |
Document input (document blocks, PDF) | Yes | Partial: base64 source only, converted to file; not tested | Document input |
| Prompt caching | Yes (cache_control) | Partial: hits from the upstream's automatic caching are reported; explicit cache_control is kept only for some models | Prompt caching |
Token counting (/v1/messages/count_tokens) | Yes | Supported | Token counting |
Structured output (output_config.format) | Yes | Not supported: in testing the field was ignored without error and the reply was not JSON; for structured output use response_format in Chat Completions | — |
Server tools (web_search, web_fetch, code_execution, tool_search) | Yes | Not supported: not converted, forwarded as-is; OpenAI-compatible upstreams do not recognize them | — |
Client built-in tools (bash, text_editor, computer, memory) | Yes | Not supported: not converted to function tools | — |
Citations and search results (citations, search_result blocks) | Yes | Not supported: not converted | — |
Files API, Message Batches, MCP connector, Skills, container | Yes | Not connected: the platform has no matching API | — |
Basic chat
When to use: one question, one answer, or the top-level system to set role and tone (in Messages, system is not part of messages).
Evidence: tested (live test account, 2026-10-19).
Request
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1500,
"system": "Answer briefly",
"messages": [{"role": "user", "content": "Explain quantum entanglement in one sentence"}]
}'max_tokens is required and must be greater than 0. For thinking models the thinking also counts toward max_tokens; if it is too small the reply is cut off during thinking.
Response
The reply text is the text of the blocks in content[] whose type is text; the stop reason is stop_reason; usage is usage. A thinking model adds a thinking block before the text block, so select by type instead of hard-coding an index.
{
"id": "msg_xxxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "The user asks in Chinese to explain quantum entanglement in one sentence… (omitted)" },
{ "type": "text", "text": "量子纠缠是指两个或多个粒子形成一种关联状态后,无论相距多远,对其中一个粒子的测量会瞬间决定另一个粒子的状态,这种超越经典物理的关联性被爱因斯坦称为“幽灵般的超距作用”。" }
],
"model": "glm-5.3-flash",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 23,
"output_tokens": 80,
"reasoning_tokens": 28
}
}The reply text above is real output for a Chinese prompt (the real request was written in Chinese).
Notes
- On OpenAI-compatible upstreams
stop_reasoncan only beend_turn/max_tokens/tool_use; hittingstop_sequencesis alsoend_turnwithstop_sequenceset tonull, so you cannot see which sequence matched. Only Anthropic-native upstreams can producestop_sequence/pause_turn/refusal. usage.reasoning_tokensis a platform extension (the official API usesoutput_tokens_details.thinking_tokens, which the platform does not emit).- The API card below is rendered from the OpenAPI spec (authentication, request-body parameter table, response schema).
Authorization
BearerAuth Authorization: Bearer $MODELSITE_API_KEY(与 OpenAI 格式共享同一 Key)
In: header
Header Parameters
Anthropic API 版本(平台不强制校验,官方 SDK 自动携带)。
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/messages" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{ "model": "string", "messages": [ {} ], "max_tokens": 1 }'取回复:content[] 里 type 为 text 的块。思考模型会多一个 thinking 块(无 signature)。取自线上真实响应(glm-5.3-flash,2026-10-19),取值为占位。
{ "id": "msg_xxxx", "type": "message", "role": "assistant", "content": [ { "type": "thinking", "thinking": "The user asks in Chinese to explain quantum entanglement in one sentence. I should respond in Chinese, concisely, as requested." }, { "type": "text", "text": "量子纠缠是指两个或多个粒子形成一种关联状态后,无论相距多远,对其中一个粒子的测量会瞬间决定另一个粒子的状态,这种超越经典物理的关联性被爱因斯坦称为“幽灵般的超距作用”。" } ], "model": "glm-5.3-flash", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 23, "output_tokens": 80, "reasoning_tokens": 28 }}Multi-turn
When to use: conversations that need context. The platform does not store conversations; put the full history in messages on each request, alternating user / assistant.
Evidence: per official docs (not tested). Multi-turn with tools was tested, see Tool use.
Request
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1500,
"messages": [
{"role": "user", "content": "What is quantum entanglement?"},
{"role": "assistant", "content": "Two particles share a state regardless of distance."},
{"role": "user", "content": "Can it be used for faster-than-light communication?"}
]
}'Response: same structure as Basic chat.
Notes
- The
roleof the first message must beuserorassistant, otherwise 400. - Whether a trailing
assistantmessage ("prefill") is accepted depends on the upstream; not tested. - The rules for sending thinking blocks back are under Thinking.
Streaming
When to use: show output token by token and lower time to first token.
Evidence: tested (live test account, 2026-10-19).
Request
curl -N https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 600,
"stream": true,
"reasoning_effort": "low",
"messages": [{"role": "user", "content": "Reply with one character: 好"}]
}'Response
Each event is an event: line plus a data: line. Concatenate text_delta.text for the text; there is no [DONE], the stream ends with message_stop.
event: message_start
data: {"type":"message_start","message":{"id":"msg_xxxx","type":"message","role":"assistant","content":[],"model":"glm-5.3-flash","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}
event: ping
data: {"type":"ping"}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"thinking":"","type":"thinking"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Simple"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":": reply \"好\"."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"text":"","type":"text"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"好"}}
event: content_block_stop
data: {"index":1,"type":"content_block_stop"}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":18,"output_tokens":9,"reasoning_tokens":6}}
event: message_stop
data: {"type":"message_stop"}Notes
- The
usageinmessage_starthas no real usage yet (all zeros); usemessage_delta.usage. - The event order is
message_start→content_block_start→content_block_delta(many) →content_block_stop→message_delta→message_stop. - In this example the thinking block has no
signature_delta; it appears only when the upstream supplies a signature. - A few events (such as the
content_block_stopabove) carry an upstream-specific numericstop_reasonfield; ignore it. - In-stream errors are sent as
event: error; see "Errors" below.
Tool use
When to use: let the model decide to call tools you define. The flow: declare tools → the model returns a tool_use block → you run it → send back a tool_result → the model gives the final answer.
Evidence: tested (live test account, 2026-10-19): a single call and returning the result. tool_choice any / tool is per official docs (not tested); the tool_choice: none and disable_parallel_tool_use conversions are covered by unit tests (not tested live).
Request (first turn)
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1500,
"tools": [{
"name": "get_weather",
"description": "Get the current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}],
"messages": [{"role": "user", "content": "What is the weather in Hangzhou tomorrow?"}]
}'Response (first turn)
stop_reason is tool_use, and the tool arguments are a parsed object input (not a string).
{
"id": "msg_xxxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "The user is asking about tomorrow's weather in Hangzhou… (omitted)" },
{
"type": "tool_use",
"id": "chatcmpl-tool-xxxxxxxx",
"name": "get_weather",
"input": { "city": "杭州" }
}
],
"model": "glm-5.3-flash",
"stop_reason": "tool_use",
"stop_sequence": null,
"usage": { "input_tokens": 160, "output_tokens": 86, "reasoning_tokens": 74 }
}Returning the tool result (second turn)
Add the assistant message's content back unchanged, then send a user message containing a tool_result block matched by tool_use_id; the tool_result block must come first in that message's content.
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1500,
"tools": [ ... same as the first turn ... ],
"messages": [
{"role": "user", "content": "What is the weather in Hangzhou tomorrow?"},
{"role": "assistant", "content": [
{"type": "tool_use", "id": "chatcmpl-tool-xxxxxxxx", "name": "get_weather", "input": {"city": "杭州"}}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "chatcmpl-tool-xxxxxxxx", "content": "Cloudy, 7-13°C"}
]}
]
}'The second-turn response is an ordinary success response (tested; this request carried reasoning_effort: "low", so there is no thinking block):
{
"id": "msg_xxxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "杭州明天天气为**多云**,气温在 **7~13°C** 之间。早晚偏凉,建议适当添加衣物保暖。" }
],
"model": "glm-5.3-flash",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 182, "output_tokens": 31 }
}When streaming, the tool_use block gives id and name in content_block_start (input is an empty object), and the arguments arrive as input_json_delta.partial_json fragments to concatenate and then parse (per official docs, not tested):
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_xxxx","name":"get_weather","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"city\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\"Hangzhou\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}Notes
tool_choice:auto→auto,any→required,tool→ a specific function.none→none;disable_parallel_tool_use: true→parallel_tool_calls: false.- The format of
tool_use.iddepends on the upstream (here achatcmpl-tool-prefix); send it back unchanged. is_errorontool_resultis not passed to OpenAI-compatible upstreams.- Only custom tools (
name+input_schema) are converted; server tools and built-in tools are not, see the scenario overview.
Thinking
When to use: you want the model to reason before answering.
Evidence: tested (live test account, 2026-10-19): glm-5.3-flash returns a thinking block by default, and with reasoning_effort: "low" thinking is essentially empty. The effect of thinking: {"type":"enabled","budget_tokens":N} depends on the upstream and was not tested.
Request: whether thinking is on and how its strength is controlled depends on the model. GLM models always think; control strength with the top-level reasoning_effort (in the Python SDK put it in extra_body; the platform forwards it as-is). For other models see the vendor pages.
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"max_tokens": 1500,
"reasoning_effort": "low",
"messages": [{"role": "user", "content": "Explain quantum entanglement in one sentence"}]
}'Response: thinking is a content[] block with type thinking, placed before text / tool_use; a real response is under Basic chat. When streaming, thinking is thinking_delta before the answer; the real events are under Streaming.
Notes
- The platform never turns thinking on or off by default. If you send
thinkingit is forwarded as-is (except GPT models, see below); if you do not, the upstream default applies. thinking: {"type": "adaptive"}is an Anthropic-specific value: it is forwarded as-is for MiniMax and dropped for other OpenAI-compatible models, which then use their own default; whenoutput_config.effortis also present the platform converts it toreasoning_effort(except MiniMax).budget_tokensis not converted toreasoning_effort(GPT / o-series are the exception: the budget is bucketed intolow/medium/high). To control strength, add the upstream-supported top-level field, such asreasoning_effort, directly in the request body.- Thinking blocks have no
signature: in testing, thinking blocks from OpenAI-compatible upstreams carry nosignature. Only Anthropic-native upstreams sign them, and then multi-turn conversations must sendthinkingblocks (withsignature) back unchanged. - Sending thinking back:
thinkingcontent blocks in earlier assistant messages are converted toreasoning_contentfor models that need it back (DeepSeek, GLM, Qwen, MiniMax); other models drop them. Your client still has to put the previous turn's thinking back in full across turns (especially tool calls). (The conversion logic here depends on code that is not yet released.) - See DeepSeek, GLM, Qwen and MiniMax for per-model values and limits.
Image input
When to use: have a vision model answer questions about an image. Choose a model that accepts image input (such as qwen3-vl-plus).
Evidence: base64 source tested (live test account, 2026-10-19, qwen3-vl-plus); url source per official docs (not tested).
Request
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-vl-plus",
"max_tokens": 300,
"messages": [{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "iVBORw0KGgo..."}},
{"type": "text", "text": "What is in this image? One sentence."}
]
}]
}'Response
{
"id": "msg_xxxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "一个红色的实心圆。" }
],
"model": "qwen3-vl-plus",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 16, "output_tokens": 8 }
}Notes
- On this endpoint
usage.input_tokenscounts only the text part, not image tokens: the same image in Chat Completions givesprompt_tokens82 (of whichimage_tokens66), while hereinput_tokensis only 16. For the full input usage use Chat Completions, or go by the bill. - With
source.typeurlthe upstream must be able to reach the address; usebase64when unsure.file_id(Files API) is not supported.
Document input (PDF)
When to use: pass a PDF or similar document to the model as an input block.
Evidence: per official docs (not tested).
Request
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-vl-plus",
"max_tokens": 600,
"messages": [{
"role": "user",
"content": [
{"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": "JVBERi0xLjQK..."}},
{"type": "text", "text": "Summarize this document"}
]
}]
}'Response: same structure as Basic chat.
Notes
- Only
documentblocks with abase64source are converted to afilecontent part;url,text,contentandfile_idsources are not converted and are forwarded as-is, which the upstream usually does not recognize. - Whether the upstream can read PDFs, and its page and size limits, vary by model and were not verified.
Prompt caching
When to use: many requests share a long prefix (system prompt, documents, tool definitions) and you want lower cost and latency.
Evidence: tested (live test account, 2026-10-19, deepseek-v4.1-flash): with cache_control on the system text block and a prefix of about 2,900 tokens that was already cached, cache_read_input_tokens was 2,816.
Request
curl https://api.modelsite.ai/v1/messages \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"max_tokens": 300,
"reasoning_effort": "low",
"system": [{
"type": "text",
"text": "You are a support assistant. Answer using these terms:\n(about 2,900 tokens of long text here)",
"cache_control": {"type": "ephemeral"}
}],
"messages": [{"role": "user", "content": "What does clause 3 say? One sentence."}]
}'Response
{
"id": "msg_xxxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "We need to answer the user's question… (omitted)" },
{ "type": "text", "text": "第3条规定:平台对所有请求按实际用量计费,调用方应当在客户端保存请求编号以便对账。" }
],
"model": "deepseek-v4.1-flash",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 87,
"output_tokens": 91,
"cache_read_input_tokens": 2816,
"reasoning_tokens": 66
}
}Notes
- Under Anthropic's convention
input_tokensexcludes the cached part: here 87 + 2,816 = 2,903, the total input. - Caching is decided by the upstream. This model's caching is automatic on the upstream side; the
cache_controlin the request has no effect on it (it is removed), and requests without the marker hit as well. - Explicit
cache_control: kept for claude / gemini-prefixed models; kept for Qwen but normalized to{"type":"ephemeral"}(ttlremoved); removed for other models. A top-levelcache_controlis not converted. The Qwen item depends on code that is not yet released. - For upstreams that write caches, usage also shows
cache_creation_input_tokensand the TTL-splitcache_creation. - The official
max_tokens: 0(cache warm-up only) is not supported.
Token counting
When to use: estimate input token count before sending a request.
Evidence: tested (live test account, 2026-10-19): sending Hi to glm-5.3-flash returned {"input_tokens": 8}. Whether the count matches the input_tokens actually billed for that model was not verified.
Request
curl https://api.modelsite.ai/v1/messages/count_tokens \
-H "Authorization: Bearer $MODELSITE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "glm-5.3-flash",
"messages": [{"role": "user", "content": "Hi"}]
}'Response
{ "input_tokens": 8 }Errors
401 invalid or missing key
{"type":"error","error":{"type":"authentication_error","message":"invalid API key"}}When the Authorization header is missing, message is missing or invalid authorization header.
402 insufficient balance
{"type":"error","error":{"type":"billing_error","message":"insufficient balance"}}429 rate limited
{"type":"error","error":{"type":"rate_limit_error","message":"too many requests"}}The response carries Retry-After (seconds); back off accordingly.
400 unknown model
{"type":"error","error":{"type":"invalid_request_error","message":"unknown model: not-a-model"}}A wrong or not-yet-live model is a 400 (not 404).
400 invalid parameters (Anthropic format)
{"type":"error","error":{"type":"invalid_request_error","message":"max_tokens is required and must be > 0"}}400s the platform validates itself include: max_tokens missing or not greater than 0; the first message's role is not user / assistant; tool_choice.type is tool without a name.
403 no access to this model
{"type":"error","error":{"type":"permission_error","message":"model 'glm-5.3-flash' is not allowed for this API key"}}503 gateway at capacity
{"type":"error","error":{"type":"overloaded_error","message":"gateway at capacity, retry later"}}Also carries Retry-After: 5.
Upstream temporarily unavailable (502 / 503): a temporarily unavailable upstream returns 502 (error.type is server_error); when all upstreams are in temporary cooldown it returns 503 (error.code is no_upstream_available). Both were seen in testing on Chat Completions; Retry shortly.
Note: on this endpoint (including /v1/messages/count_tokens), errors the gateway produces (401 / 402 / 403 / 429 / unknown model / 503 capacity) and errors from the routing layer (parameters, upstream failures and so on) all use the Anthropic-style envelope, with Anthropic's official type names; insufficient balance is billing_error.
If a streaming request fails after output has started, the HTTP status is already 200 and the error is sent as an in-stream event: event: error with data {"type":"error","error":{"type":"<type>","message":"<message>"}}. Full error codes and handling advice: Errors.
Which format?
| Existing code | Recommended entry |
|---|---|
| OpenAI / LangChain (OpenAI) / most frameworks | /chat/completions |
| Anthropic SDK | /messages (this page) |
Both entries reach the same models.
Unsupported or different parameters
The request is converted to Chat Completions form and then sent to the upstream. The platform does not drop parameters against a parameter allowlist and does not clamp values: apart from the hard-coded conversions in the table below, official parameters are not converted and are forwarded as unknown top-level fields; whether the upstream recognizes them, ignores them or rejects them (4xx) is up to it.
| Parameter | Behavior | Notes |
|---|---|---|
max_tokens | Required, must be > 0 | Missing or 0 returns 400. The official 0 (cache warm-up only) is not supported. |
messages | Partial | The first role must be user or assistant. Content blocks converted: text, image (base64 / url source), tool_use, tool_result, thinking, audio (base64), document (base64 source only, converted to file); document with url / text / content sources, file_id sources, search_result and other blocks are not converted and are forwarded as-is. is_error on tool_result is not passed to OpenAI-compatible upstreams |
system | Rewritten | String or array of text blocks, converted to leading system messages, one per text block; non-text blocks are ignored |
stop_sequences | Rewritten | Converted to stop. When an OpenAI-compatible upstream hits a stop sequence, the response stop_reason is end_turn and stop_sequence is null; the matched sequence is not available |
temperature top_p top_k | Pass-through | Forwarded to the upstream, which decides the valid range; out-of-range values may be rejected by the upstream |
tool_choice | Partial | auto → auto, any → required, tool → a specific function. none → none; disable_parallel_tool_use: true → parallel_tool_calls: false |
tools | Partial | Custom tools (name + input_schema) are converted to function tools. Server tools (such as web_search_20250305, code_execution_*, text_editor_*, bash_*, computer_*) are not converted and are forwarded as-is; OpenAI-compatible upstreams usually do not recognize them |
thinking | Rewritten | enabled / disabled forwarded as-is; adaptive is kept only for MiniMax and dropped for other OpenAI-compatible models. GPT / o-series convert thinking to reasoning_effort. See "Thinking" above (depends on unreleased code) |
output_config.effort | Rewritten | Converted to reasoning_effort only when thinking.type = "adaptive" and there is no top-level reasoning_effort (except MiniMax) |
output_config.format (structured output) | Not converted | Not turned into response_format; the whole output_config is forwarded as an unknown field; in testing it was ignored and the reply was not JSON. For structured output use response_format in Chat Completions |
metadata | Partial | Only metadata.user_id is used, converted to user |
service_tier | Value not mapped | Placed into service_tier as-is; Anthropic's auto / standard_only differ from OpenAI-style values, so the upstream may reject or ignore it |
cache_control (top level) | Not converted | Forwarded as an unknown field; OpenAI-compatible upstreams usually ignore it |
container mcp_servers speed inference_geo diagnostics | Not converted | Forwarded as unknown fields; container and mcp_servers mean nothing to OpenAI-compatible upstreams |
cache_control on content and system blocks | Removed or rewritten | Kept for claude / gemini-prefixed models; kept for Qwen but normalized to {"type":"ephemeral"} (ttl removed); deleted for all other models (the Qwen part depends on unreleased code) |
thinking blocks in history | Handled per model | Converted to reasoning_content for DeepSeek, GLM, Qwen, MiniMax; kept for claude / gemini-prefixed models; deleted for other models. redacted_thinking is deleted as well (except claude / gemini-prefixed models) (depends on unreleased code) |
Response stop_details, usage.output_tokens_details, usage.service_tier, usage.inference_geo | Not returned | Newer official response fields the platform does not emit; for thinking tokens see the platform extension usage.reasoning_tokens |
Platform extensions
What follows is defined by the platform and does not exist in the official Anthropic API.
Authentication and request headers
- Authentication uses
Authorization: Bearer, not the officialx-api-key(see the note at the top). - The gateway only lets these request headers through and drops all others:
Authorization,Content-Type,Accept,Accept-Encoding,Origin,User-Agent,Anthropic-Beta,OpenAI-Beta.Anthropic-Betais passed to the upstream;anthropic-versionis neither validated nor forwarded. X-Request-ID: a value you send is kept and echoed in the response header; if absent, the platform generates one.
Request parameters
| Parameter | Notes |
|---|---|
Top-level reasoning_effort and other vendor fields | To control thinking strength, add the upstream-supported top-level field in the request body; it is forwarded as-is |
/v1/messages/count_tokens | Available |
Response headers
| Header | Notes |
|---|---|
X-Request-ID | Request ID; give it to us when troubleshooting |
Retry-After | Suggested back-off seconds on 429; at gateway capacity it returns 503 with Retry-After: 5 |
Content-Type: text/event-stream, Cache-Control: no-cache, X-Accel-Buffering: no | Streaming responses; when a stream is idle the gateway sends a : heartbeat comment frame every 15 seconds, which SSE clients ignore automatically |
The platform does not return X-RateLimit-* or anthropic-ratelimit-* quota headers.
Response fields
| Field | Notes |
|---|---|
usage.reasoning_tokens | Thinking token count (the official API uses output_tokens_details.thinking_tokens, which the platform does not emit) |
usage.cache_read_input_tokens, cache_creation_input_tokens, cache_creation | Cache usage; present only when non-zero |
usage.cost, usage.cost_details | This endpoint does not return cost fields; for cost use Chat Completions or Responses |
Automatic behavior
- Streaming requests turn on
include_usageinternally; usage is inmessage_delta.usage. - Automatic retry and upstream switching on upstream failure follow the same rules as Chat Completions: only before you receive any response content; parameter errors and content-safety blocks are not retried.
- When the balance runs out during streaming, the stream is cut and you then receive a
data: {"error":{"type":"insufficient_quota","message":"balance exhausted"}}frame anddata: [DONE]; this frame is OpenAI-style, not Anthropic'sevent: error.
Chat Completions (OpenAI-compatible)
POST /v1/chat/completions — OpenAI Chat Completions, with complete examples by scenario
DeepSeek Models
Scenario-based examples for deepseek-v4.1-flash / deepseek-v4-flash-0731 / deepseek-v4-pro: chat, thinking, tool calls, JSON output, prefix completion, caching, image input