# Messages > POST /messages reference (Anthropic Messages format): headers, every request parameter with its translation status, response fields, stream events, errors, limits and examples. Source: https://kiln.bricksum.com/docs/en/api-reference/messages `POST https://api.bricksum.com/v1/messages` Generates a model response in the Anthropic Messages format. The gateway translates the request to Chat Completions, sends it to the model, and translates the response back. Each parameter below is marked with what the translation does with it today. ## Authentication Authentication is the same on every endpoint the gateway fronts, and is documented on the [API Reference](https://kiln.bricksum.com/docs/en/api-reference) page. ## Request body | Status | Description | | --- | --- | | supported | The gateway handles it, or a published measurement shows it works. | | passed through | Forwarded to the model server; not verified by Kiln. | | ignored | Accepted with 200, but not applied. The rest of the response is normal. | | not supported | Rejected with an error, or not working on the models named in the description. | ### Common parameters These are documented once on the [API Reference](https://kiln.bricksum.com/docs/en/api-reference) page. Listed here with what this endpoint does differently. | Field | Type | required | Default | Status | Description | | --- | --- | --- | --- | --- | --- | | `model` | string | | | supported | Optional here. When the field is empty or missing, the gateway uses its configured default model. An unknown ID — an Anthropic model ID included — is rejected with 404 (not_found_error). | | `messages` | array | required | | supported | An empty or missing array is rejected with 400 (invalid_request_error). content is a string or an array of content blocks, in the shapes below. A message with any other role is treated as a user message. | | `messages[].role (user message)` | string | required | | supported | Set to "user". Allowed values: user | | `messages[].content[].type = "text" (user message)` | object | | | supported | Text. Consecutive text blocks are joined into one string. | | `messages[].content[].type = "image" (user message)` | object | | | supported | Translated to an image_url part. A base64 source becomes a data: URL, and a url source is forwarded as sent. It works only on models whose input includes images; see the Models page. | | `messages[].content[].type = "tool_result" (user message)` | object | | | supported | Translated to a tool message whose tool_call_id is tool_use_id, in block order. content is a string or text blocks; blocks of other types inside it are dropped. is_error is not forwarded. | | `messages[].content[].type = "document" (user message)` | object | | | ignored | Dropped before the request reaches the model. The same applies to block types that are not listed here. | | `messages[].role (assistant message)` | string | required | | supported | Set to "assistant". Allowed values: assistant | | `messages[].content[].type = "text" (assistant message)` | object | | | supported | An earlier model reply. | | `messages[].content[].type = "tool_use" (assistant message)` | object | | | supported | Translated to a tool_calls entry with the same id and name. input is sent as a compact JSON string. | | `messages[].content[].type = "thinking" (assistant message)` | object | | | ignored | thinking and redacted_thinking blocks are dropped. The model does not receive its earlier reasoning text. | | `messages[].role (system message)` | string | required | | supported | A message with role "system" inside messages becomes a system message at that position. Only its text is kept. Allowed values: system | | `max_tokens` | integer | | | supported | A value above 32,768 is lowered to 32,768 instead of being rejected, which keeps room for the prompt in the context window. When the field is missing or 0, it is not forwarded and the model server applies its own default. | | `stream` | boolean | | `false` | supported | The stream is a sequence of Anthropic events, listed under Stream events below. | | `tools` | array | | | supported | A tool with a name and an input_schema is translated to a function definition. | | `tools[].name (custom tool)` | string | required | | supported | The name the model uses in tool_use blocks. | | `tools[].description (custom tool)` | string | | | supported | What the tool does. The model server rejects a function without a description with 400. | | `tools[].input_schema (custom tool)` | object | required | | supported | JSON Schema of the tool input. It becomes the function parameters. | | `tools[].type (built-in tool)` | string | | | ignored | A tool without an input_schema, such as bash_20250124, is skipped. The model cannot run Anthropic server tools, so it does not see them. | | `tool_choice = {"type": "auto"}` | object | | | supported | Translated to tool_choice: "auto", and forwarded only when at least one custom tool is sent. | | `tool_choice = {"type": "any"} \| {"type": "tool", ...}` | object | | | not supported | Forced tool choice. any is translated to tool_choice: "required", and tool to the named function. On qwen3-32b (measured 2026-09-26), forced tool choice fails without a clear error: tool_choice "required" returns HTTP 500 with a plain-text "Internal Server Error" body, and naming a function returns 200 with empty arguments (finish_reason "stop"). Neither is a 4xx, so do not retry it — send tool_choice "auto" only. | | `tool_choice = {"type": "none"}` | object | | | passed through | Translated to tool_choice: "none" and forwarded to the model server; not verified by Kiln. | ### Only on this endpoint | Field | Type | required | Default | Status | Description | | --- | --- | --- | --- | --- | --- | | `system` | string \| array | | | supported | System prompt: a string, or an array of text blocks. Each text block becomes one system message at the start of the conversation, in order. Blocks of other types are dropped. | | `tool_choice.disable_parallel_tool_use` | boolean | | | passed through | true is translated to parallel_tool_calls: false and forwarded to the model server; this translation was not measured by Kiln. On qwen3-32b the parallel_tool_calls setting is not applied, which is not the same as replies holding one call: even with parallel_tool_calls: false, one reply can carry several tool calls. Answer every entry in tool_calls with its own tool message. | | `thinking` | object | | | supported | type "enabled" or "adaptive" is translated to reasoning_effort: "medium". budget_tokens is not used. With any other type, no reasoning_effort is sent. | | `output_config.effort` | string | | | supported | Translated to reasoning_effort, and takes precedence over thinking. max, xhigh and ultra are lowered to high. Other values are dropped. Allowed values: low, medium, high | | `temperature` | number | | | passed through | Copied to the chat request and forwarded to the model server; not verified by Kiln. | | `top_p` | number | | | passed through | Copied to the chat request and forwarded to the model server; not verified by Kiln. | | `top_k` | integer | | | ignored | Accepted, but dropped. The translation has no matching chat parameter. | | `stop_sequences` | array | | | ignored | Translated to stop, which is accepted but not applied today. Generation continues past the stop string, and stop_sequence in the response is null. | | `cache_control` | object | | | ignored | Accepted on any block, tool or system entry, and dropped. Prefix caching on the model server does not depend on it; see usage.cache_read_input_tokens. | | `metadata` | object | | | ignored | Accepted, but dropped. The same applies to context_management. | Fields that are not listed here are dropped before the request reaches the model server. They do not cause an error. ## Response body | Field | Type | Description | | --- | --- | --- | | `id` | string | "msg_" followed by the ID of the underlying chat completion. | | `type` | string | "message". | | `role` | string | "assistant". | | `model` | string | The model ID of the request. | | `content[]` | array | Output blocks in this order: one thinking block when the model returned reasoning text, one text block, then one tool_use block per tool call. Do not assume content[0] is text. | | `content[].thinking` | string | Reasoning text of a thinking block. The block carries no signature. | | `content[].text` | string | Generated text of a text block. | | `content[].input` | object | Arguments of a tool_use block, next to id and name. Arguments that are not a JSON object are kept as a string under _malformed_arguments. | | `stop_reason` | string | "end_turn" (finish_reason stop), "max_tokens" (length) or "tool_use" (tool_calls, or any reply that has tool calls). Other finish reasons map to "end_turn". | | `stop_sequence` | null | null in every response. | | `usage.input_tokens` | integer | Input tokens, copied from prompt_tokens of the chat completion. | | `usage.output_tokens` | integer | Output tokens, including reasoning tokens. | | `usage.cache_read_input_tokens` | integer | Input tokens served from the prefix cache. 0 when the model server reports none. | | `X-Neocloud-Generation-Id` | header | Response header with the ID of the usage event for this request. Store it with your logs. The Messages usage object has no cost field. | ## Stream events With stream: true, each event has an event: line with its name and a data: line with a JSON object of the same type. The order is message_start, then content_block_start, content_block_delta and content_block_stop for each block, then message_delta and message_stop. One block is open at a time. There is no data: [DONE] line. | Field | Type | Description | | --- | --- | --- | | `message_start` | event | Opens the message: id, model, empty content. Its usage holds input_tokens: 0 and output_tokens: 0, because the model server reports usage only at the end. | | `content_block_start` | event | Opens the block at index. content_block.type is thinking, text or tool_use; a tool_use block carries id and name. | | `content_block_delta` | event | New content of the open block. delta.type is text_delta (text), thinking_delta (thinking) or input_json_delta (partial_json). Concatenate partial_json per block, then parse it. | | `content_block_stop` | event | Closes the block at index. | | `message_delta` | event | delta.stop_reason and usage.output_tokens. The model server sends usage in a chunk after the one with finish_reason, so the gateway reads the stream to its end before it writes this event. The stream does not report input tokens. | | `message_stop` | event | The last event of a complete message. | | `ping` | event | Sent after 15 seconds without data from the model server, for example during a long reasoning pause. It can arrive at any point, including before message_start. Ignore it. | | `error` | event | The last event of a failed stream, in the Anthropic error envelope. A model server error, a read failure or a stream that ends before completion gives api_error. No message_stop follows. | ## Errors After authentication, errors use the Anthropic envelope: {"type": "error", "error": {"type", "message"}}. Branch on the HTTP status and error.type, not on the message text. Rows with an empty error.type cell come from the gateway before the request reaches this endpoint, and use the body shown under the table. | Status | error.type | Cause | What to do | | --- | --- | --- | --- | | 400 | `invalid_request_error` | The body is not valid JSON, messages is empty or missing, or a message content is neither a string nor an array of blocks. | Fix the payload. Do not retry as sent. | | 400 | `invalid_request_error` | The model server rejected the translated request, for example more than the model's context window (the Models page lists it per model). error.message carries the model server's message. | Shorten the conversation or fix the parameters. | | 404 | `not_found_error` | The model ID is not in the catalog. | Check the ID against GET /models. | | 405 | `invalid_request_error` | The method is not POST. | Send the request with POST. | | 429 | `rate_limit_error` | The organization already has 8 requests in flight. No rate-limit headers are sent. | Back off exponentially and retry. | | 502 | `api_error` | The model backend returned an error or was unreachable, or its response could not be translated. | Retry with exponential backoff. | | 503 | `api_error` | No model backend is available. | Retry with exponential backoff. | 401, 402, 403 and 429 come from the gateway before the request reaches this endpoint. They are listed once on the [API Reference](https://kiln.bricksum.com/docs/en/api-reference) page. Authentication, credit, budget and rate-limit (requests/min, tokens/min) errors are returned by the gateway before the Anthropic translation. Their body is not the Anthropic envelope: ```json { "error": { "message": "invalid API key", "type": "authorization" } } ``` Every status code is listed on the [Errors](https://kiln.bricksum.com/docs/en/errors) page. ## Limits - Output: max_tokens is capped at 32,768. A larger value is lowered, not rejected. - Metering: a Messages request is metered and billed as one chat completion, with the same token counts and the same per-organization limit of 8 concurrent requests. Context length, request body size, timeouts and the per-organization rate limits apply here as well; they are listed once on the [API Reference](https://kiln.bricksum.com/docs/en/api-reference) page. Headers and retry guidance for 429 are on the [Rate limits](https://kiln.bricksum.com/docs/en/rate-limits) page. ## Examples ### Request **cURL** ```bash curl https://api.bricksum.com/v1/messages \ -H "x-api-key: $KILN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-32b", "max_tokens": 800, "system": "Answer briefly.", "messages": [ {"role": "user", "content": "Hello!"} ] }' ``` **Python** ```python import os from anthropic import Anthropic # base_url is the API origin without /v1: the SDK appends /v1/messages. # api_key is sent as x-api-key. Use auth_token=... to send a Bearer token. client = Anthropic( base_url="https://api.bricksum.com", api_key=os.environ["KILN_API_KEY"], ) message = client.messages.create( model="qwen3-32b", max_tokens=800, system="Answer briefly.", messages=[{"role": "user", "content": "Hello!"}], ) for block in message.content: if block.type == "text": print(block.text) print(message.usage) ``` **TypeScript** ```typescript import Anthropic from "@anthropic-ai/sdk"; // baseURL is the API origin without /v1: the SDK appends /v1/messages. // apiKey is sent as x-api-key. Use authToken to send a Bearer token. const client = new Anthropic({ baseURL: "https://api.bricksum.com", apiKey: process.env.KILN_API_KEY, }); const message = await client.messages.create({ model: "qwen3-32b", max_tokens: 800, system: "Answer briefly.", messages: [{ role: "user", content: "Hello!" }], }); for (const block of message.content) { if (block.type === "text") console.log(block.text); } console.log(message.usage); ``` ### Response **200 OK** ```json { "id": "msg_7c1f0b9e2a", "type": "message", "role": "assistant", "model": "qwen3-32b", "content": [ {"type": "thinking", "thinking": "The user said hello. Reply briefly."}, {"type": "text", "text": "Hello! How can I help you today?"} ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 21, "output_tokens": 34, "cache_read_input_tokens": 0 } } ``` **text/event-stream** ```text event: message_start data: {"message":{"content":[],"id":"msg_7c1f0b9e2a","model":"qwen3-32b","role":"assistant","stop_reason":null,"stop_sequence":null,"type":"message","usage":{"input_tokens":0,"output_tokens":0}},"type":"message_start"} event: ping data: {"type":"ping"} event: content_block_start data: {"content_block":{"text":"","type":"text"},"index":0,"type":"content_block_start"} event: content_block_delta data: {"delta":{"text":"Hello","type":"text_delta"},"index":0,"type":"content_block_delta"} event: content_block_delta data: {"delta":{"text":"! How can I help you today?","type":"text_delta"},"index":0,"type":"content_block_delta"} event: content_block_stop data: {"index":0,"type":"content_block_stop"} event: message_delta data: {"delta":{"stop_reason":"end_turn","stop_sequence":null},"type":"message_delta","usage":{"output_tokens":34}} event: message_stop data: {"type":"message_stop"} ``` **400 Bad Request** ```json { "type": "error", "error": { "type": "invalid_request_error", "message": "messages must contain at least one message" } } ```