Messages (Anthropic)
An Anthropic Messages-format adapter. Change only the base URL to attach Claude Code or the Anthropic SDK.
PleumRouter accepts requests in the Anthropic Messages format and routes them. Existing Anthropic SDK code and Claude Code work unchanged — just point the base URL at PleumRouter. Requests and responses follow the Anthropic Messages schema.
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?"}]
}'import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["PLEUM_API_KEY"],
base_url="https://apirouter.pleum.ai", # root URL; the SDK appends /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_AUTH_TOKEN. Leave ANTHROPIC_API_KEY empty.
export PLEUM_API_KEY=plm_xxxxxxxxxxxxxxxx
export ANTHROPIC_BASE_URL="https://apirouter.pleum.ai"
export ANTHROPIC_AUTH_TOKEN="$PLEUM_API_KEY"
export ANTHROPIC_API_KEY="" # must be explicitly empty
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. Both headers work.
Create a message#
| Parameter | Type | Required | Description |
|---|---|---|---|
| thinking | object | Optional | enabled | adaptive | disabled. enabled works only on models with manual thinking and adaptive only on models with adaptive thinking; a mismatch returns 400. enabled requires budget_tokens (1,024–32,000). See Reasoning Models for each model's mode. |
| 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). image blocks work only with models whose input_modalities include image (see Vision). In a tool_result content array, only text (text blocks or plain strings) is accepted; other blocks such as image or document return 400. |
| 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. |
| stream | boolean | Optional | If true, returns an Anthropic SSE streaming response. |
| tools | array | Optional | Tools in Anthropic format {name, description, input_schema}. |
| tool_choice | object | Optional | Controls tool use as {type: any | none | tool}. |
| output_config | object | Optional | Only effort (low, medium, high, xhigh, max) is read and passed on as the chosen model's reasoning-effort setting. Other keys are ignored. |
| metadata | object | Optional | Accepted but not forwarded to the provider. |
modelstringRequiredmessagesobject[]Required
rolestringRequireduser | assistant
contentstring | ContentBlock[]Requiredcontent[]objectOptional
text / image / tool_use / tool_result blocks. Image blocks work only with models whose input_modalities include image.
text
typeliteralRequiredexample: text
textstringOptionaltool_use
typeliteralRequiredexample: tool_use
idstringOptionalnamestringOptionalinputobjectOptionaltool_result
typeliteralRequiredexample: tool_result
tool_use_idstringOptionalcontentstring | object[]OptionalIn an array, only text blocks and plain strings are accepted. Other block types such as image or document return 400.
max_tokensintegerOptionalMinimum 1. There is no global upper bound. A value above the model's max_output is lowered to that maximum instead of failing; a 400 is returned only when input plus output cannot fit the context window.
default: 4096
systemstring | object[]OptionaltemperaturenumberOptionaltop_pnumberOptionalstop_sequencesstring[]OptionalstreambooleanOptionaldefault: false
toolsobject[]Optionaltool_choiceobjectOptionalmetadataobjectOptionalthinkingobjectOptional
enabled works only on models with manual thinking, and adaptive only on models with adaptive thinking. A mismatch returns 400.
enabled
typeliteralRequiredexample: enabled
budget_tokensintegerRequiredrange: 1024–32000
displaystringOptionalenum: omitted
adaptive
typeliteralRequiredexample: adaptive
displaystringOptionalenum: omitted
disabled
typeliteralRequiredexample: disabled
When thinking is active, PleumRouter always requests upstream display: "omitted". Only while thinking is active, a request with tools may use only tool_choice auto or none. In that case, required/specific-tool forcing and a final assistant-message prefill are rejected with 400 before billing starts.
curl https://apirouter.pleum.ai/v1/messages \
-H "x-api-key: $PLEUM_API_KEY" \
-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 end_turn for a normal finish, max_tokens when the token limit is reached, and tool_use when a tool is called.
{
"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, then content_block_start / content_block_delta (text_delta) / content_block_stop, then message_delta, and finally 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. usage.input_tokens in message_start is an estimate. 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: $PLEUM_API_KEY" \
-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 (decimal in 0.1 steps) 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.