Messages (Anthropic)
An Anthropic Messages-format adapter. Change only the base URL to attach Claude Code or the Anthropic SDK.
PleumRouter accepts inbound requests in the Anthropic Messages format and routes them internally. Existing Anthropic SDK code and Claude Code work unchanged — just point the base URL at PleumRouter. Requests and responses follow the Anthropic Messages schema and are converted to the OpenAI format internally.
Connecting#
For the Anthropic SDK, set base_url to the root URL https://apirouter.pleum.ai. The SDK appends /v1/messages for you.
Anthropic Messages API
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: "plm_xxxxxxxxxxxxxxxx",
// Root origin — the SDK appends /v1/messages
baseURL: "https://apirouter.pleum.ai",
});
const message = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Why is the sky blue?" }],
});
console.log(message.content);Anthropic Messages API
from anthropic import Anthropic
client = Anthropic(
api_key="plm_xxxxxxxxxxxxxxxx",
# Root origin — the SDK appends /v1/messages
base_url="https://apirouter.pleum.ai",
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Why is the sky blue?"}],
)
print(message.content)Anthropic Messages API
curl https://apirouter.pleum.ai/v1/messages \
-H "Authorization: Bearer plm_xxxxxxxxxxxxxxxx" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Why is the sky blue?"}]
}'from anthropic import Anthropic
client = Anthropic(
api_key="plm_...",
base_url="https://apirouter.pleum.ai", # 루트 — SDK가 /v1/messages를 덧붙임
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)For Claude Code, set ANTHROPIC_BASE_URL to the root without /v1, and put your plm_ key in ANTHROPIC_API_KEY.
export ANTHROPIC_BASE_URL="https://apirouter.pleum.ai"
export ANTHROPIC_API_KEY="plm_..."
claude/v1 to the base URL. The SDK appends /v1/messages again, producing /v1/v1/messages, and the request fails. Always use just the root https://apirouter.pleum.ai.Authenticate with your plm_ API key via the Authorization: Bearer or x-api-key header. Claude Code sends both, and either one works.
Create a message#
| Parameter | Type | Required | Description |
|---|---|---|---|
| thinking | object | Optional | Native enabled | adaptive | disabled union. enabled requires budget_tokens (1,024–32,000). |
| model | string | Required | Model ID. See the full list at GET /v1/models. |
| messages | array | Required | Array of {role, content}. content is a string or a block array (text / image / tool_use / tool_result). |
| max_tokens | integer | Optional | Maximum number of tokens to generate. Default 4096. |
| system | string | array | Optional | System prompt. A string or a block array. |
| temperature | number | Optional | Sampling temperature. |
| top_p | number | Optional | Nucleus (cumulative probability) sampling. |
| stop_sequences | array | Optional | Array of stop strings. Mapped internally to stop. |
| stream | boolean | Optional | If true, returns an Anthropic SSE streaming response. |
| tools | array | Optional | Tools in Anthropic format {name, description, input_schema}. Converted to the OpenAI format internally. |
| tool_choice | object | Optional | Controls tool use as {type: any | none | tool}. |
| metadata | object | Optional | Accepted but not forwarded to the provider. |
When thinking is active, PleumRouter always requests upstream display: "omitted". With tools, only tool_choice auto or none is allowed. required/specific-tool forcing and a final assistant-message prefill are rejected with 400 before a credit hold.
curl https://apirouter.pleum.ai/v1/messages \
-H "x-api-key: plm_..." \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"thinking": {"type": "adaptive", "display": "omitted"},
"system": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "Hello"}
]
}'The response follows the Anthropic Messages schema. content is a block array, and token usage is returned as usage.input_tokens / usage.output_tokens. Native thinking is returned only as opaque {type:"thinking", thinking:"", signature} or {type:"redacted_thinking", data} blocks; plaintext thought is never exposed. On a tool continuation, return those blocks byte-for-byte, including any _pleum_source_model extension.
stop_reason is mapped from the internal finish reason — stop → end_turn, length → max_tokens, tool_calls → tool_use.
{
"id": "msg_01abc...",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-6",
"content": [
{"type": "thinking", "thinking": "", "signature": "<opaque>"},
{"type": "text", "text": "Hello! How can I help you?"}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 5
}
}Streaming#
With stream: true, the Anthropic SSE event sequence is sent — message_start → content_block_start / content_block_delta (text_delta) / content_block_stop → message_delta → message_stop. Omitted thinking is sent only as an empty thinking block, then signature_delta, then stop; it never emits thinking_delta or plaintext thought. Redacted blocks use start/stop only. No cost headers are included in stream mode.
event: message_start
data: {"type":"message_start","message":{"id":"msg_01abc","type":"message","role":"assistant","model":"claude-sonnet-4-6","content":[],"stop_reason":null,"usage":{"input_tokens":12,"output_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"<opaque>"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Hello! How can I help you?"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}}
event: message_stop
data: {"type":"message_stop"}Count tokens#
Takes the same body as /v1/messages and returns {"input_tokens": <int>}. This value is a heuristic estimate, not the output of a real tokenizer.
curl https://apirouter.pleum.ai/v1/messages/count_tokens \
-H "x-api-key: plm_..." \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [
{"role": "user", "content": "Hello"}
]
}'{
"input_tokens": 12
}Billing#
Cost is not included in the response body. Instead it is returned in the response headers X-Cost-Krw (int) and X-Cost-Usd (float), along with x-request-id. usage.output_tokens is the only billable output quantity; thinking detail is never charged or added again.