# Messages > POST /messages 레퍼런스(Anthropic Messages 형식)입니다. 헤더, 요청 파라미터별 변환 상태, 응답 필드, 스트림 이벤트, 에러, 제한, 예제를 설명합니다. Source: https://kiln.bricksum.com/docs/ko/api-reference/messages `POST https://api.bricksum.com/v1/messages` Anthropic Messages 형식으로 모델 응답을 생성합니다. 게이트웨이는 요청을 Chat Completions로 변환해 모델에 보내고, 응답을 다시 Messages 형식으로 변환합니다. 아래 각 파라미터에는 현재 이 변환이 그 값을 어떻게 처리하는지 표시했습니다. ## 인증 게이트웨이가 처리하는 엔드포인트의 인증 방식은 모두 같으며, [API 레퍼런스](https://kiln.bricksum.com/docs/ko/api-reference) 페이지에서 설명합니다. ## 요청 본문 | 상태 | 설명 | | --- | --- | | 지원 | 게이트웨이가 처리하거나, 공개된 실측에서 동작이 확인된 항목입니다. | | 전달만 함 | 모델 서버로 전달됩니다. Kiln이 동작을 검증하지 않았습니다. | | 무시됨 | 200으로 수락되지만 적용되지 않습니다. 응답의 나머지는 정상입니다. | | 미지원 | 에러로 거부되거나, 설명에 적힌 모델에서 동작하지 않습니다. | ### 공통 파라미터 아래 파라미터의 정의는 [API 레퍼런스](https://kiln.bricksum.com/docs/ko/api-reference) 페이지에 한 번만 정리했습니다. 여기에는 이 엔드포인트에서 다른 점만 적었습니다. | 필드 | 타입 | 필수 | 기본값 | 상태 | 설명 | | --- | --- | --- | --- | --- | --- | | `model` | string | | | 지원 | 여기서는 선택 사항입니다. 이 필드가 비어 있거나 없으면 게이트웨이에 설정된 기본 모델을 사용합니다. 카탈로그에 없는 ID는 Anthropic 모델 ID를 포함해 404(not_found_error)로 거부됩니다. | | `messages` | array | 필수 | | 지원 | 배열이 비어 있거나 없으면 400(invalid_request_error)으로 거부됩니다. content는 문자열 또는 아래 형태의 content 블록 배열입니다. 그 밖의 role을 가진 메시지는 user 메시지로 처리됩니다. | | `messages[].role (user 메시지)` | string | 필수 | | 지원 | "user"로 지정합니다. 허용 값: user | | `messages[].content[].type = "text" (user 메시지)` | object | | | 지원 | 텍스트입니다. 연속된 text 블록은 하나의 문자열로 합쳐집니다. | | `messages[].content[].type = "image" (user 메시지)` | object | | | 지원 | image_url 파트로 변환됩니다. base64 source는 data: URL이 되고, url source는 보낸 값 그대로 전달됩니다. 이미지 입력을 받는 모델에서만 동작합니다. 모델 페이지를 확인하세요. | | `messages[].content[].type = "tool_result" (user 메시지)` | object | | | 지원 | tool_use_id를 tool_call_id로 하는 tool 메시지로 변환되며, 블록 순서를 유지합니다. content는 문자열 또는 text 블록이고, 그 안의 다른 타입 블록은 제거됩니다. is_error는 전달되지 않습니다. | | `messages[].content[].type = "document" (user 메시지)` | object | | | 무시됨 | 모델에 도달하기 전에 제거됩니다. 여기에 없는 블록 타입도 마찬가지입니다. | | `messages[].role (assistant 메시지)` | string | 필수 | | 지원 | "assistant"로 지정합니다. 허용 값: assistant | | `messages[].content[].type = "text" (assistant 메시지)` | object | | | 지원 | 이전 모델 응답입니다. | | `messages[].content[].type = "tool_use" (assistant 메시지)` | object | | | 지원 | 같은 id와 name을 가진 tool_calls 항목으로 변환됩니다. input은 공백을 줄인 JSON 문자열로 전달됩니다. | | `messages[].content[].type = "thinking" (assistant 메시지)` | object | | | 무시됨 | thinking과 redacted_thinking 블록은 제거됩니다. 모델은 이전 reasoning 텍스트를 받지 않습니다. | | `messages[].role (system 메시지)` | string | 필수 | | 지원 | messages 안에서 role이 "system"인 메시지는 그 위치의 system 메시지가 됩니다. 텍스트만 유지됩니다. 허용 값: system | | `max_tokens` | integer | | | 지원 | 32,768을 넘는 값은 거부되지 않고 32,768로 낮춰지며, 그만큼 컨텍스트 안에 프롬프트 공간이 남습니다. 이 필드가 없거나 0이면 전달되지 않고 모델 서버의 기본값이 적용됩니다. | | `stream` | boolean | | `false` | 지원 | 스트림은 Anthropic 이벤트의 연속이며, 아래 스트림 이벤트에 정리했습니다. | | `tools` | array | | | 지원 | name과 input_schema가 있는 툴은 함수 정의로 변환됩니다. | | `tools[].name (커스텀 툴)` | string | 필수 | | 지원 | 모델이 tool_use 블록에서 사용하는 이름입니다. | | `tools[].description (커스텀 툴)` | string | | | 지원 | 툴이 하는 일입니다. 모델 서버는 description이 없는 함수를 400으로 거부합니다. | | `tools[].input_schema (커스텀 툴)` | object | 필수 | | 지원 | 툴 입력의 JSON Schema입니다. 함수의 parameters가 됩니다. | | `tools[].type (내장 툴)` | string | | | 무시됨 | bash_20250124처럼 input_schema가 없는 툴은 건너뜁니다. 모델은 Anthropic 서버 툴을 실행할 수 없으므로 이 툴을 받지 않습니다. | | `tool_choice = {"type": "auto"}` | object | | | 지원 | tool_choice: "auto"로 변환되며, 커스텀 툴이 하나 이상 있을 때만 전달됩니다. | | `tool_choice = {"type": "any"} \| {"type": "tool", ...}` | object | | | 미지원 | 강제 툴 선택입니다. any는 tool_choice: "required"로, tool은 지정한 함수로 변환됩니다. qwen3-32b에서는 강제 툴 선택이 명확한 에러 없이 실패합니다(2026-09-26 실측). tool_choice를 "required"로 보내면 본문이 평문 "Internal Server Error"인 HTTP 500이 오고, 함수를 지정하면 arguments가 빈 200(finish_reason "stop")이 옵니다. 둘 다 4xx가 아니므로 재시도로 해결되지 않습니다. tool_choice는 "auto"만 보내세요. | | `tool_choice = {"type": "none"}` | object | | | 전달만 함 | tool_choice: "none"으로 변환되어 모델 서버로 전달됩니다. Kiln이 동작을 검증하지 않았습니다. | ### 이 엔드포인트에만 있는 파라미터 | 필드 | 타입 | 필수 | 기본값 | 상태 | 설명 | | --- | --- | --- | --- | --- | --- | | `system` | string \| array | | | 지원 | 시스템 프롬프트입니다. 문자열 또는 text 블록 배열로 보냅니다. 각 text 블록은 대화 맨 앞의 system 메시지가 되며 순서를 유지합니다. 다른 타입의 블록은 제거됩니다. | | `tool_choice.disable_parallel_tool_use` | boolean | | | 전달만 함 | true는 parallel_tool_calls: false로 변환되어 모델 서버로 전달됩니다. 이 변환 경로는 Kiln이 측정하지 않았습니다. qwen3-32b에서는 parallel_tool_calls 설정이 적용되지 않습니다. 이는 응답에 툴 호출이 하나만 온다는 뜻이 아닙니다. parallel_tool_calls: false를 보내도 한 응답에 툴 호출이 여러 건 올 수 있습니다. tool_calls의 모든 항목에 각각 tool 메시지로 답하세요. | | `thinking` | object | | | 지원 | type이 "enabled" 또는 "adaptive"이면 reasoning_effort: "medium"으로 변환됩니다. budget_tokens는 사용되지 않습니다. 그 밖의 type이면 reasoning_effort를 보내지 않습니다. | | `output_config.effort` | string | | | 지원 | reasoning_effort로 변환되며 thinking보다 우선합니다. max, xhigh, ultra는 high로 낮춰집니다. 그 밖의 값은 제거됩니다. 허용 값: low, medium, high | | `temperature` | number | | | 전달만 함 | chat 요청에 복사되어 모델 서버로 전달됩니다. Kiln이 동작을 검증하지 않았습니다. | | `top_p` | number | | | 전달만 함 | chat 요청에 복사되어 모델 서버로 전달됩니다. Kiln이 동작을 검증하지 않았습니다. | | `top_k` | integer | | | 무시됨 | 수락되지만 제거됩니다. 이 변환에는 대응하는 chat 파라미터가 없습니다. | | `stop_sequences` | array | | | 무시됨 | stop으로 변환되지만, stop은 현재 수락만 되고 적용되지 않습니다. stop 문자열 이후에도 생성이 계속되며, 응답의 stop_sequence는 null입니다. | | `cache_control` | object | | | 무시됨 | 블록, 툴, system 항목 어디에 있어도 수락되고 제거됩니다. 모델 서버의 프리픽스 캐시는 이 값과 무관하게 동작합니다. usage.cache_read_input_tokens를 확인하세요. | | `metadata` | object | | | 무시됨 | 수락되지만 제거됩니다. context_management도 마찬가지입니다. | 여기에 없는 필드는 요청이 모델 서버에 도달하기 전에 제거됩니다. 에러가 발생하지는 않습니다. ## 응답 본문 | 필드 | 타입 | 설명 | | --- | --- | --- | | `id` | string | "msg_" 뒤에 내부 chat completion의 ID가 붙습니다. | | `type` | string | "message"입니다. | | `role` | string | "assistant"입니다. | | `model` | string | 요청에 지정한 모델 ID입니다. | | `content[]` | array | 출력 블록입니다. 모델이 reasoning 텍스트를 반환하면 thinking 블록 하나, 이어서 text 블록 하나, 그다음 툴 호출마다 tool_use 블록 하나가 이 순서로 옵니다. content[0]이 text라고 가정하지 마세요. | | `content[].thinking` | string | thinking 블록의 reasoning 텍스트입니다. 이 블록에는 signature가 없습니다. | | `content[].text` | string | text 블록의 생성된 텍스트입니다. | | `content[].input` | object | tool_use 블록의 인자입니다. id, name과 함께 옵니다. JSON 객체가 아닌 인자는 _malformed_arguments 아래에 문자열로 유지됩니다. | | `stop_reason` | string | "end_turn"(finish_reason stop), "max_tokens"(length), "tool_use"(tool_calls 또는 툴 호출이 있는 응답) 중 하나입니다. 그 밖의 finish_reason은 "end_turn"이 됩니다. | | `stop_sequence` | null | 모든 응답에서 null입니다. | | `usage.input_tokens` | integer | 입력 토큰 수입니다. chat completion의 prompt_tokens를 그대로 옮긴 값입니다. | | `usage.output_tokens` | integer | 출력 토큰 수입니다. reasoning 토큰을 포함합니다. | | `usage.cache_read_input_tokens` | integer | 프리픽스 캐시에서 처리된 입력 토큰 수입니다. 모델 서버가 보고하지 않으면 0입니다. | | `X-Neocloud-Generation-Id` | header | 이 요청의 usage 이벤트 ID를 담은 응답 헤더입니다. 로그와 함께 저장하세요. Messages의 usage 객체에는 cost 필드가 없습니다. | ## 스트림 이벤트 stream: true이면 각 이벤트는 이름을 담은 event: 줄과, 같은 type의 JSON 객체를 담은 data: 줄로 구성됩니다. 순서는 message_start, 블록마다 content_block_start, content_block_delta, content_block_stop, 그다음 message_delta, message_stop입니다. 블록은 한 번에 하나만 열립니다. data: [DONE] 줄은 없습니다. | 필드 | 타입 | 설명 | | --- | --- | --- | | `message_start` | event | 메시지를 시작합니다. id, model, 빈 content를 담습니다. 모델 서버가 usage를 마지막에만 보고하므로 이 이벤트의 usage는 input_tokens: 0, output_tokens: 0입니다. | | `content_block_start` | event | index 위치의 블록을 엽니다. content_block.type은 thinking, text, tool_use 중 하나이며, tool_use 블록은 id와 name을 담습니다. | | `content_block_delta` | event | 열려 있는 블록의 새 내용입니다. delta.type은 text_delta(text), thinking_delta(thinking), input_json_delta(partial_json) 중 하나입니다. partial_json은 블록별로 이어 붙인 뒤 파싱하세요. | | `content_block_stop` | event | index 위치의 블록을 닫습니다. | | `message_delta` | event | delta.stop_reason과 usage.output_tokens를 담습니다. 모델 서버는 finish_reason이 담긴 청크 다음 청크에 usage를 보내므로, 게이트웨이는 스트림을 끝까지 읽은 뒤 이 이벤트를 씁니다. 스트림은 입력 토큰 수를 보고하지 않습니다. | | `message_stop` | event | 완료된 메시지의 마지막 이벤트입니다. | | `ping` | event | 모델 서버에서 15초 동안 데이터가 오지 않으면 전송됩니다(예: 긴 reasoning 구간). message_start 이전을 포함해 어느 시점에든 올 수 있습니다. 무시하면 됩니다. | | `error` | event | 실패한 스트림의 마지막 이벤트이며 Anthropic 에러 형식을 사용합니다. 모델 서버 에러, 읽기 실패, 완료 전에 끝난 스트림은 api_error가 됩니다. 이후 message_stop은 오지 않습니다. | ## 에러 인증을 통과한 뒤의 에러는 Anthropic 형식({"type": "error", "error": {"type", "message"}})으로 반환됩니다. 메시지 문구가 아니라 HTTP 상태와 error.type으로 분기하세요. error.type 칸이 비어 있는 행은 요청이 이 엔드포인트에 도달하기 전에 게이트웨이가 반환하는 에러이며, 표 아래에 있는 본문 형식을 사용합니다. | 상태 | error.type | 원인 | 조치 | | --- | --- | --- | --- | | 400 | `invalid_request_error` | 본문이 올바른 JSON이 아니거나, messages가 비어 있거나 없거나, 메시지의 content가 문자열도 블록 배열도 아닙니다. | 요청 본문을 수정하세요. 그대로 재시도하지 마세요. | | 400 | `invalid_request_error` | 모델 서버가 변환된 요청을 거부했습니다(예: 모델 페이지에 나오는 모델별 컨텍스트 길이를 넘는 컨텍스트). error.message에 모델 서버의 메시지가 담깁니다. | 대화를 줄이거나 파라미터를 수정하세요. | | 404 | `not_found_error` | 모델 ID가 카탈로그에 없습니다. | GET /models로 ID를 확인하세요. | | 405 | `invalid_request_error` | 메서드가 POST가 아닙니다. | POST로 요청하세요. | | 429 | `rate_limit_error` | 조직에서 이미 8개의 요청이 처리 중입니다. 레이트 리밋 헤더는 포함되지 않습니다. | 지수 백오프로 재시도하세요. | | 502 | `api_error` | 모델 백엔드가 에러를 반환했거나 연결할 수 없거나, 응답을 변환할 수 없습니다. | 지수 백오프로 재시도하세요. | | 503 | `api_error` | 사용 가능한 모델 백엔드가 없습니다. | 지수 백오프로 재시도하세요. | 401, 402, 403, 429는 요청이 이 엔드포인트에 도달하기 전에 게이트웨이가 반환합니다. 이 에러들은 [API 레퍼런스](https://kiln.bricksum.com/docs/ko/api-reference) 페이지에 한 번만 정리했습니다. 인증, 크레딧, 예산, 레이트 리밋(분당 요청 수, 분당 토큰 수) 에러는 Anthropic 형식으로 변환되기 전에 게이트웨이가 반환합니다. 본문은 Anthropic 형식이 아닙니다. ```json { "error": { "message": "invalid API key", "type": "authorization" } } ``` 전체 상태 코드는 [에러](https://kiln.bricksum.com/docs/ko/errors) 페이지에 있습니다. ## 제한 - 출력: max_tokens의 상한은 32,768입니다. 더 큰 값은 거부되지 않고 낮춰집니다. - 계측: Messages 요청은 chat completion 1건으로 계측되고 과금됩니다. 토큰 수와 조직별 동시 요청 8개 제한도 동일하게 적용됩니다. 컨텍스트 길이, 요청 본문 크기, 타임아웃, 조직별 레이트 리밋도 여기에 그대로 적용되며, [API 레퍼런스](https://kiln.bricksum.com/docs/ko/api-reference) 페이지에 한 번만 정리했습니다. 429의 헤더와 재시도 방법은 [레이트 리밋](https://kiln.bricksum.com/docs/ko/rate-limits) 페이지에 있습니다. ## 예제 ### 요청 **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); ``` ### 응답 **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" } } ```