본문으로 건너뛰기

Messages (Anthropic)

An Anthropic Messages-format adapter. Change only the base URL to attach Claude Code or the Anthropic SDK.

POST/v1/messages
POST/v1/messages/count_tokens

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.

messages.ts

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 SDK (Python)
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.

Claude Code
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
Do not append /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#

ParameterTypeRequiredDescription
thinkingobjectOptionalenabled | 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.
modelstringRequiredModel ID. See the full list at GET /v1/models.
messagesarrayRequiredArray 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_tokensintegerOptionalMaximum number of tokens to generate. Default 4096.
systemstring | arrayOptionalSystem prompt. A string or a block array.
temperaturenumberOptionalSampling temperature.
top_pnumberOptionalNucleus (cumulative probability) sampling.
stop_sequencesarrayOptionalArray of stop strings.
streambooleanOptionalIf true, returns an Anthropic SSE streaming response.
toolsarrayOptionalTools in Anthropic format {name, description, input_schema}.
tool_choiceobjectOptionalControls tool use as {type: any | none | tool}.
output_configobjectOptionalOnly effort (low, medium, high, xhigh, max) is read and passed on as the chosen model's reasoning-effort setting. Other keys are ignored.
metadataobjectOptionalAccepted but not forwarded to the provider.
modelstringRequired
messagesobject[]Required
rolestringRequired

user | assistant

contentstring | ContentBlock[]Required
content[]objectOptional

text / image / tool_use / tool_result blocks. Image blocks work only with models whose input_modalities include image.

text

typeliteralRequired

example: text

textstringOptional

tool_use

typeliteralRequired

example: tool_use

idstringOptional
namestringOptional
inputobjectOptional

tool_result

typeliteralRequired

example: tool_result

tool_use_idstringOptional
contentstring | object[]Optional

In an array, only text blocks and plain strings are accepted. Other block types such as image or document return 400.

max_tokensintegerOptional

Minimum 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[]Optional
temperaturenumberOptional
top_pnumberOptional
stop_sequencesstring[]Optional
streambooleanOptional

default: false

toolsobject[]Optional
tool_choiceobjectOptional
metadataobjectOptional
thinkingobjectOptional

enabled works only on models with manual thinking, and adaptive only on models with adaptive thinking. A mismatch returns 400.

enabled

typeliteralRequired

example: enabled

budget_tokensintegerRequired

range: 1024–32000

displaystringOptional

enum: omitted

adaptive

typeliteralRequired

example: adaptive

displaystringOptional

enum: omitted

disabled

typeliteralRequired

example: 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.

request
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.

200 OK
{
  "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.

SSE stream
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.

request
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"}
    ]
  }'
200 OK
{
  "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.