Messages API
Use POST /v1/messages to call a chat model with the Anthropic Messages request and response shapes. The endpoint works with direct Anthropic models and with models served through the OpenAI Responses protocol. The model you choose decides the backend protocol; the endpoint you call decides the shape of the request and response.
This version supports text conversations, as one response or as a stream. Tools, images and documents, and most optional settings are not available yet. A request that uses one of them is rejected before it reaches the model.
Before you begin
You need:
- the API URL shown in the platform portal
- an API key with
invokeaccess to a chat model - the model ID from
GET /v1/models
Authenticate with your platform API key as a bearer token: Authorization: Bearer <key>. The x-api-key header is not supported. For an Anthropic model, the gateway sends the connector's Anthropic key; your platform key never reaches Anthropic.
curl -sS "$API_BASE_URL/v1/messages" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL_ID",
"max_tokens": 1024,
"system": "Answer in one sentence.",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}'
With the Anthropic Python SDK, set the base URL to the API URL without /v1 and pass the key as an auth token:
import anthropic
client = anthropic.Anthropic(base_url="https://api.<your-domain>", auth_token="cm_api_…")
message = client.messages.create(
model="YOUR_MODEL_ID",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)
Supported request
| Member | Support |
|---|---|
model | Required. A model or model alias ID. Preset agents are not supported on this endpoint. |
max_tokens | Required integer. null is rejected. 0 is sent to the model as given. |
messages | Required and not empty. Roles user and assistant. A final assistant message (a prefill) returns 400 unless the model is an Anthropic model. |
system | A string, or a list of text blocks joined with a blank line. |
temperature, top_p | Sent to the model when set. |
stream | true or false. See Streaming. |
Message content is a string or a list of blocks. User messages accept text blocks. Assistant messages accept text, thinking and redacted_thinking blocks, so you can send back earlier answers, including their thinking.
A request that ends with an assistant message asks the model to continue that message. Models served through the Responses protocol cannot continue a message, so the gateway rejects the request instead of returning a new answer. For an Anthropic model the gateway sends the request as given, and the model decides; current Claude models reject a prefilled final assistant message.
Any other request member, including tools, tool_choice, thinking, top_k, stop_sequences, metadata and output_config, returns 400. So does any other block type, such as image, document, tool_use or tool_result, and any cache_control or citations on a block. The error message names what is not supported.
Response
The response is a Messages message object:
{
"id": "msg_…",
"type": "message",
"role": "assistant",
"model": "YOUR_MODEL_ID",
"content": [{"type": "text", "text": "Paris is the capital of France."}],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 21,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens": 9
}
}
model is the model ID you sent. stop_reason is one of:
| Stop reason | Meaning |
|---|---|
end_turn | The model finished its answer. |
max_tokens | The answer reached max_tokens. content holds the partial answer. |
refusal | The model refused. content holds any text the model wrote before it refused, then the refusal explanation as a text block when the model gives one. |
Any other ending, such as a model failure or a stop reason this API cannot represent, returns an error instead of a message.
usage.input_tokens counts input that was not read from or written to the prompt cache. Cache writes and reads are reported separately; your usage accounting counts all three as input.
Streaming
Set "stream": true to receive the message as server-sent events, in the Messages streaming format:
message_startwith an empty message. Itsusagecounts are placeholders, all0.- For each content block,
content_block_start, itscontent_block_deltaevents andcontent_block_stop.indexcounts the blocks of the response. Text arrives astext_delta, thinking asthinking_deltafollowed by onesignature_delta. Aredacted_thinkingblock arrives whole in itscontent_block_start. message_deltawithstop_reasonand the finalusage, totaled over the whole request and equal to the unary response's, thenmessage_stop.
With the Anthropic Python SDK, client.messages.stream() sends the request with stream set and accumulates the events:
with client.messages.stream(
model="YOUR_MODEL_ID",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
) as stream:
for text in stream.text_stream:
print(text, end="")
message = stream.get_final_message()
The gateway sends ping events while the model is silent. The blocks, stop reason and usage you accumulate from the stream equal the unary response for the same answer, with one exception. When a Responses model sends a reasoning item only in its finished response, after answer text has already streamed, the stream delivers that thinking block after the text, while the unary response keeps the model's order. The block's thinking and signature are the same in both. The gateway has not confirmed that a model accepts the reasoning when it is sent back in this changed order.
With an Anthropic model, the gateway learns that a block is complete only when the model's message ends. Block starts and deltas arrive as the model produces them, but every block's signature_delta and content_block_stop arrive after the last block has started, just before message_delta. Deltas for an earlier block can therefore follow the start of a later one. Accumulate deltas by index, as the Anthropic SDKs do. When an Anthropic model refuses, the stream and the unary response both hold the text written before the refusal. Thinking already streamed also stays in the stream, without a signature, while the unary response leaves it out. The TypeScript SDK's per-event callbacks, such as signature and contentBlock, can name the wrong block in this order, but finalMessage() and the accumulated message are correct.
Errors found before the stream starts, such as an invalid request or a denied model, return an ordinary JSON error with its HTTP status. An error after the stream has started ends it with one error event carrying the same error object, and no message_stop. The stream starts before the gateway calls the model, so when the model service rejects the request, for example with 429, the stream ends with an error event: the HTTP status is already 200, and no Retry-After header is sent. If the client disconnects, the gateway stops the model request.
Conversation history
The endpoint is stateless. It stores no conversation and creates no conversation items. To continue a conversation, send the earlier messages again, including assistant content exactly as you received it.
Assistant thinking and redacted_thinking blocks must be sent back unchanged.
Thinking from Responses models
Messages has no field for the reasoning items that OpenAI Responses models return. The gateway returns each one as a thinking block: its thinking is the reasoning summary followed by the reasoning text, separated by blank lines, and its signature starts with cmgw. and carries the reasoning item. Send the block back unchanged; the gateway restores the original reasoning item for the model. A cmgw. signature is not an Anthropic signature: sending it to an Anthropic model returns 400. A malformed cmgw. signature also returns 400.
Errors
Errors use the Messages error shape:
{"type": "error", "error": {"type": "invalid_request_error", "message": "max_tokens is required"}}
| 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 |
529 | overloaded_error |
| Other | api_error |
A missing or invalid credential is rejected before the request reaches the gateway and does not use the Messages error shape: a missing or unrecognized credential returns 403, and a recognized API key that is invalid returns 401.
When the model service rejects a request that does not stream, the gateway returns its status with the public message described in Model backend errors, and forwards a Retry-After header. Provider messages are not returned.
When the model service fails after it started answering, the gateway returns the same public message. An invalid request, including one too long for the model, returns 400; an authentication failure 401; a permission failure 403; and a rate limit 429, without a Retry-After header. Any other failure returns 502 with api_error.