Anthropic Messages API¶
SMG serves the Anthropic Messages API at /v1/messages and counts input tokens at /v1/messages/count_tokens. What happens to a request depends on the backend: with gRPC workers SMG implements the API itself, with HTTP workers it forwards the request to an engine that implements the Messages API, and with the Anthropic backend it forwards the request to the Anthropic API.
Endpoints¶
| Method | Path | Description |
|---|---|---|
POST |
/v1/messages |
Create a message. Set "stream": true to receive Server-Sent Events. |
POST |
/v1/messages/count_tokens |
Count the input tokens of a Messages request without generating. |
Both endpoints go through the same authentication and admission control as the other inference endpoints.
Supported Backends¶
| Backend | Configure with | /v1/messages |
/v1/messages/count_tokens |
|---|---|---|---|
| gRPC workers | grpc:// worker URLs |
SMG renders, tokenizes, and parses (details) | 501 Not Implemented |
| ZMQ workers | ipc:// worker URLs |
Same as gRPC workers | 501 Not Implemented |
| HTTP workers | http:// or https:// worker URLs |
Forwarded to the worker | Forwarded to one worker |
| HTTP PD | --pd-disaggregation with HTTP workers |
Prefill/decode dual dispatch | Forwarded to one prefill worker |
| Anthropic API | --backend anthropic |
Forwarded to the Anthropic API, with an optional SMG-run MCP tool loop | 501 Not Implemented |
HTTP workers must implement the Messages API themselves; SMG forwards the request without translating it.
In IGW mode (--enable-igw), SMG picks the backend per request from the workers that serve the requested model. A model served by an external Anthropic worker goes to the Anthropic API backend (see External Providers). The other external providers answer /v1/messages with 501.
Authentication¶
SMG's own authentication is separate from the credentials it passes to backends:
| Hop | Headers | Behavior |
|---|---|---|
| Client to SMG | Authorization: Bearer <key> |
Checked when --api-key or --tenant-api-key is set (--tenant-api-key is a Rust binary flag; the pip smg launch does not accept it). SMG does not read x-api-key. A missing or unknown bearer token gets 401 Unauthorized. |
| SMG to HTTP workers | Authorization |
The client's header is forwarded with the other allowlisted headers; x-api-key is not. See Authentication for worker API keys. |
| SMG to the Anthropic API | x-api-key, Authorization |
Forwarded exactly as the client sent them. SMG adds no key of its own, so the client supplies the Anthropic key. |
Anthropic SDKs send their key in x-api-key, which the Anthropic API backend passes upstream. When gateway authentication is on, clients must also send the gateway key as Authorization: Bearer <key>; on the Anthropic API backend that header is forwarded to Anthropic as well.
Create a Message¶
POST /v1/messagesRequest Body¶
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | Model ID. Must not be empty. |
messages |
array | Yes | At least one message, each with a role (user, assistant, or system) and content (a string or an array of content blocks). |
max_tokens |
integer | Yes | Maximum number of tokens to generate. Must be at least 1. |
system |
string or array | No | System prompt, as a string or an array of text blocks. |
stream |
boolean | No | Stream the response as Server-Sent Events. |
temperature |
number | No | Sampling temperature. |
top_p |
number | No | Nucleus sampling. |
top_k |
integer | No | Sample only from the top K tokens. |
stop_sequences |
array of strings | No | Sequences that stop generation. |
tools |
array | No | Tool definitions; see Tool Use. |
tool_choice |
object | No | {"type": "auto"}, {"type": "any"}, {"type": "tool", "name": "..."}, or {"type": "none"}. Every value except none requires tools, and tool must name one of them. |
thinking |
object | No | {"type": "enabled", "budget_tokens": N}, {"type": "adaptive"}, or {"type": "disabled"}. enabled and adaptive also take display (summarized or omitted). |
metadata |
object | No | Request metadata (user_id). |
service_tier |
string | No | auto or standard_only. |
container |
object | No | Container for code execution (beta). |
mcp_servers |
array | No | MCP servers for the request (beta). Required when tools has an mcp_toolset entry; see MCP Tools. |
rid |
string | No | Extension: request ID passed to the backend for log correlation. |
SMG validates the body before routing and answers 400 when a required field is missing or a rule above is broken. The one exception is a body that the HTTP router streams through unparsed; see HTTP and PD Workers. Fields SMG does not model, such as context_management or output_config, are kept and forwarded to HTTP workers and the Anthropic API.
Content blocks in messages may be text, image, document, tool_use, tool_result, thinking, redacted_thinking, server_tool_use, search_result, web_search_tool_result, tool_search_tool_result, or tool_reference. Any other block type is rejected with 400. A system-role message inside messages is accepted, because some clients send one mid-conversation.
Example Request¶
curl http://localhost:30000/v1/messages \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"max_tokens": 1024,
"system": "You are a concise assistant.",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
]
}'curl http://localhost:30000/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
]
}'Response¶
{
"id": "msg_abc123",
"type": "message",
"role": "assistant",
"content": [
{"type": "text", "text": "There is no single answer..."}
],
"model": "meta-llama/Llama-3.1-8B-Instruct",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 27,
"output_tokens": 112,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
}
}With gRPC workers SMG builds this response (details). With HTTP workers and the Anthropic API it comes from the backend.
Tool Use¶
Define each tool with a JSON Schema input_schema:
{
"tools": [
{
"name": "get_weather",
"description": "Get the current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
],
"tool_choice": {"type": "auto"}
}A tool call comes back as a tool_use block, with stop_reason set to tool_use:
{
"content": [
{
"type": "tool_use",
"id": "toolu_0b7e5f1c9a2d4e6f8a1b3c5d",
"name": "get_weather",
"input": {"city": "Paris"}
}
],
"stop_reason": "tool_use"
}Return the result in the next user message as a tool_result block whose tool_use_id is the id of the call.
Streaming¶
Set "stream": true to receive the response as Server-Sent Events. Each event has an event: line naming its type and a data: line with the JSON payload. The stream ends with message_stop; there is no data: [DONE] line.
| Event | Payload |
|---|---|
message_start |
The message, with empty content |
content_block_start |
A new content block at index: thinking, text, or tool_use |
content_block_delta |
A text_delta, thinking_delta, or input_json_delta (a fragment of the tool input JSON) for the block at index |
content_block_stop |
The block at index is complete |
message_delta |
stop_reason, stop_sequence, and the final usage |
message_stop |
End of the message |
ping |
Keep-alive; relayed from upstream, never sent by the gRPC path |
error |
A failure after the stream started; see Errors |
HTTP workers and the Anthropic API produce their own events, which SMG relays; the Anthropic API can send block and delta types beyond this table. A gRPC worker's stream for a short text answer looks like this:
event: message_start
data: {"type":"message_start","message":{"id":"msg_abc123","type":"message","role":"assistant","content":[],"model":"meta-llama/Llama-3.1-8B-Instruct","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"There is no single answer"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":112,"input_tokens":27,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}
event: message_stop
data: {"type":"message_stop"}Count Tokens¶
POST /v1/messages/count_tokensReturns the number of input tokens a Messages request would use, without generating. SMG does not count the tokens itself: it forwards the request to a worker that implements the endpoint and relays the worker's response (smg-project/smg#2638).
Request Body¶
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | Model ID; selects the worker. |
messages |
array | Yes | Conversation, in the same format as /v1/messages. |
system |
string or array | No | System prompt. |
tools |
array | No | Tool definitions. |
tool_choice |
object | No | Tool selection. |
thinking |
object | No | Thinking configuration. |
There is no max_tokens. Any other field, such as context_management or mcp_servers, is forwarded to the worker unchanged.
Example¶
curl http://localhost:30000/v1/messages/count_tokens \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"system": "You are a concise assistant.",
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
]
}'{
"input_tokens": 27
}Backend Support¶
| Deployment | Behavior |
|---|---|
| HTTP workers | Routed like any other request (routing policy, retries, circuit breaker, load tracking) to one worker's /v1/messages/count_tokens. 404 (model_not_found) when no worker serves the model. |
| HTTP PD | Sent to one prefill worker. No decode worker is involved and no bootstrap fields are added. 503 (no_prefill_servers) when no prefill worker is available. |
| gRPC or ZMQ workers | 501 Not Implemented |
| Anthropic API backend | 501 Not Implemented |
| IGW mode | A model with an external worker goes to that provider's backend (501). Otherwise SMG chooses between the model's HTTP and HTTP PD workers; a model with neither, including an unknown model, gets 404 (smg-project/smg#2659). |
Request Headers¶
Headers specific to the Messages API:
| Header | Effect |
|---|---|
X-SMG-MCP |
enabled makes SMG run the MCP tool loop itself for a request whose tools include an mcp_toolset entry, on the Anthropic API backend. See MCP Tools and MCP. Other backends ignore it, and SMG does not forward it. |
anthropic-version, anthropic-beta |
Not read by SMG. Forwarded to HTTP and PD workers (smg-project/smg#2638) and to the Anthropic API. |
x-api-key |
Not read by SMG. Forwarded only to the Anthropic API; see Authentication. |
Forwarded Headers¶
For HTTP and PD workers, SMG forwards only these request headers (it sets Content-Type itself):
| Header | Purpose |
|---|---|
Authorization |
Credentials for the worker; see Authentication |
anthropic-version, anthropic-beta |
API version and beta features for engines that serve /v1/messages (smg-project/smg#2638) |
x-request-id, x-correlation-id, and any header starting with x-request-id- |
Request correlation |
traceparent, tracestate |
W3C trace context |
x-smg-routing-key |
SMG routing hint |
Every other request header, including x-api-key, is dropped. The Anthropic API backend forwards a different set: Authorization, x-api-key, anthropic-version, and anthropic-beta, and nothing else. gRPC and ZMQ workers receive a tokenized request, not HTTP headers.
gRPC and ZMQ Workers¶
With gRPC workers (SGLang, vLLM, TensorRT-LLM, TokenSpeed, or MLX), and ZMQ workers that use the same pipeline, SMG implements the Messages API itself. It renders the conversation with the model's chat template, tokenizes it, sends token IDs to the engine, and builds the Anthropic response from the generated tokens. The pipeline serves regular, PD (--pd-disaggregation), and EPD (--epd-disaggregation) deployments. SMG needs the model's tokenizer (--model-path); see gRPC Workers.
| Request field | Handling |
|---|---|
system, messages |
Rendered with the chat template. Text and image blocks in user turns, tool_use and thinking blocks in assistant turns, and tool_result blocks are converted. A system-role message inside messages keeps its position. Images go through SMG's multimodal pipeline. |
max_tokens, temperature, top_p, top_k |
Sent to the engine as sampling parameters. |
stop_sequences |
Sent as stop strings to vLLM, TensorRT-LLM, and TokenSpeed gRPC workers. For SGLang gRPC workers and ZMQ workers, SMG matches them itself and sends only single-token stops to the engine, as stop token IDs. |
tools |
Custom tools (those with an input_schema) are passed to the chat template and the tool parser; with a tool_choice of tool, only that tool is passed to the chat template. Other tool types, such as mcp_toolset, bash, text editor, web search, and tool search, are dropped. |
tool_choice |
Mapped to Chat Completions semantics: auto to auto, any to required, tool to that function, none to none. SMG enforces any and tool with constrained decoding. |
thinking |
enabled and adaptive turn on the chat template's thinking mode and run the model's reasoning parser. disabled turns thinking mode off. Without thinking, the template default applies. budget_tokens and display are not used. |
metadata, service_tier, container, mcp_servers, extra fields such as output_config |
Not sent to the engine. |
MLX workers answer 400 to a request with images, with stop_sequences, or with a tool_choice of any or tool.
How the response is built:
- Tool calls and reasoning are extracted by SMG's tool and reasoning parsers, which SMG picks for the model or you set with
--tool-call-parserand--reasoning-parser. Athinkingblock comes first, then text, thentool_useblocks. Thinking blocks carry an emptysignature. tool_useids use Anthropic'stoolu_prefix: a parser id of the formcall_<suffix>is returned astoolu_<suffix>, the same in streaming and non-streaming responses. Model-specific id formats are returned unchanged (smg-project/smg#2111).stop_reasonistool_usewhen the output has tool calls,stop_sequencewhen one ofstop_sequencesended generation (the matched string is instop_sequence),max_tokenswhen the token limit was reached, andend_turnotherwise.usagereportscache_creation_input_tokensandcache_read_input_tokensas0, nevernull. In a stream,message_startcarries zero counters, and the finalmessage_deltacarriesoutput_tokensand, once the engine has reported it,input_tokens(smg-project/smg#2269).- Context window: when the selected worker advertises its context length, SMG rejects a prompt with more tokens than the window with
400context_length_exceededbefore dispatch. In PD mode the smaller of the prefill and decode windows applies. Whether the prompt plusmax_tokensmust fit is left to the engine (smg-project/smg#2618). - Tenant rate limiting, when enabled, covers this endpoint; see Tenant Rate Limiting.
HTTP and PD Workers¶
With HTTP workers, SMG forwards /v1/messages to the selected worker. It normally buffers the request, validates it, and re-serializes it: fields SMG does not model are kept, a --model-alias name is replaced by the canonical model ID, and numbers keep the values the client sent. When the HTTP router instead streams the body through (see Request Streaming), it forwards the raw body and leaves validation to the worker. Either way, only the allowlisted headers are forwarded, and the worker's response, including error responses and SSE streams, is relayed unchanged.
With --pd-disaggregation and HTTP workers, /v1/messages uses the same prefill/decode dual dispatch as chat completions: SMG selects a prefill and decode pair and dispatches the request to both (smg-project/smg#2250). See PD Disaggregation.
Anthropic API Backend¶
Point SMG at the Anthropic API:
smg launch --backend anthropic --worker-urls https://api.anthropic.comClients then call SMG as in the Anthropic API example.
- SMG sends each request to
<worker URL>/v1/messages, so the worker URL is the API origin without/v1. The request carries the client'sx-api-key,Authorization,anthropic-version, andanthropic-betaheaders; SMG adds none of them. - Streaming responses are relayed as they arrive. Non-streaming responses are parsed into SMG's Messages schema and re-serialized; a body that does not parse returns
502with codeparse_error. - An upstream error is returned with its status and body.
/v1/messages/count_tokensreturns501.
MCP Tools¶
SMG can run MCP tools for Messages requests on the Anthropic API backend. gRPC workers drop mcp_toolset tools, and HTTP workers receive them in the request body.
SMG runs the tool loop itself when the request has the X-SMG-MCP: enabled header and tools contains at least one mcp_toolset entry. Without the header, mcp_servers and the mcp_toolset entries are forwarded to Anthropic, together with the client's anthropic-beta header, and Anthropic's MCP connector runs the tools.
curl http://localhost:30000/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "X-SMG-MCP: enabled" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Search the web for SMG and summarize the top result."}
],
"mcp_servers": [
{"type": "url", "name": "search", "url": "https://mcp.example.com/mcp"}
],
"tools": [
{"type": "mcp_toolset", "mcp_server_name": "search"}
]
}'| Field | Description |
|---|---|
mcp_servers[].name |
Server name that mcp_toolset entries refer to. |
mcp_servers[].url |
http:// or https:// URL. SMG connects over Streamable HTTP. A URL containing /sse selects the legacy SSE transport, which SMG can no longer connect to. |
mcp_servers[].authorization_token |
Sent to the MCP server as Authorization: Bearer <token>. |
mcp_toolset.mcp_server_name |
The server whose tools the model may call. |
mcp_toolset.default_config.enabled |
Whether the server's tools are enabled (default true). |
mcp_toolset.configs |
Per-tool settings keyed by tool name. When present, only the tools listed here are exposed, each if its enabled is true (or unset, falling back to default_config.enabled). |
defer_loading in default_config or configs |
Copied onto the tools SMG passes to the model. |
What SMG does:
- Connects to the servers in
mcp_servers. If none connects, it returns502with codemcp_connection_failed. - Replaces
mcp_serversand themcp_toolsetentries with the enabled MCP tools as regular tools, and setstool_choicetoautoif the request had none. - Runs each tool the model calls, sends the results back to the model, and repeats until the model stops calling tools, for at most 10 rounds of tool calls. If the model still calls tools after the 10th round, a non-streaming request fails with
502(mcp_max_iterations). A stream ends with anerrorevent right after the 10th round, without calling the model again. - Returns each call as an
mcp_tool_useblock (id prefixedmcptoolu_) followed by itsmcp_tool_resultblock, then the model's final content:
{
"content": [
{
"type": "mcp_tool_use",
"id": "mcptoolu_01AbCdEf",
"name": "web_search",
"server_name": "search",
"input": {"query": "SMG"}
},
{
"type": "mcp_tool_result",
"tool_use_id": "mcptoolu_01AbCdEf",
"content": [{"type": "text", "text": "..."}],
"is_error": null
},
{"type": "text", "text": "The top result describes..."}
],
"stop_reason": "end_turn"
}In a stream, SMG forwards the upstream events, emits the model's tool calls as mcp_tool_use blocks and each result as an mcp_tool_result block, and ends with a single message_delta and message_stop. SMG does not accept mcp_tool_use or mcp_tool_result blocks in messages (see the accepted block types under Request Body).
Tool calls use policy-only approval: SMG never pauses the request to ask the client. The gateway runs its built-in default policy, which allows every tool, because the policy section of the MCP configuration file is not applied in v1.11.0. See MCP for server configuration.
Errors¶
SMG produces Anthropic's error format ({"type": "error", ...}) only inside an SSE stream. Elsewhere the shape depends on where the error comes from:
| Source | Status | Body |
|---|---|---|
| Body is not valid JSON, a required field is missing, or a field has the wrong type | 400 |
{"error": {"message": "...", "type": "invalid_request_error", "code": "json_parse_error"}} |
Body fails validation (for example max_tokens of 0, empty messages, tool_choice without tools) |
400 |
{"error": {"message": "...", "type": "invalid_request_error", "code": 400}} |
| Gateway authentication fails | 401 |
Empty |
| SMG itself: routing, the gRPC pipeline, the MCP tool loop | Varies | The standard JSON error envelope, plus an X-SMG-Error-Code header |
| HTTP or PD worker, or the Anthropic API | Upstream status | The upstream body, unchanged |
The standard envelope names the HTTP status in type and repeats code in the X-SMG-Error-Code header. For example, an over-long prompt on a gRPC worker:
{
"error": {
"type": "Bad Request",
"code": "context_length_exceeded",
"message": "This model's maximum context length is 32768 tokens. However, your request has 40210 input tokens. Please reduce the length of the input.",
"param": null
}
}A few gateway answers are plain text instead: the 404 when no router serves the request in IGW mode, the 501 from a backend that does not serve the endpoint, and the /v1/messages/count_tokens answer to a body that does not parse (400 for invalid JSON, 415 without Content-Type: application/json, 422 for a missing field or a field of the wrong type).
Errors that SMG detects before the stream starts are returned as ordinary HTTP error responses, not as a stream, even when stream is true. After the stream has started, the gRPC path and SMG's MCP tool loop report a failure as an Anthropic error event and end the stream. The MCP tool loop starts its stream before it first calls the model, so even an upstream error on that first call arrives this way:
event: error
data: {"type":"error","error":{"type":"api_error","message":"..."}}HTTP workers and the Anthropic API stream their own error events, which SMG relays.