Skip to content

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

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

Claude Code
export ANTHROPIC_BASE_URL="https://apirouter.pleum.ai"
export ANTHROPIC_API_KEY="plm_..."
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. Claude Code sends both, and either one works.

Create a message#

ParameterTypeRequiredDescription
thinkingobjectOptionalNative enabled | adaptive | disabled union. enabled requires budget_tokens (1,024–32,000).
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).
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. Mapped internally to stop.
streambooleanOptionalIf true, returns an Anthropic SSE streaming response.
toolsarrayOptionalTools in Anthropic format {name, description, input_schema}. Converted to the OpenAI format internally.
tool_choiceobjectOptionalControls tool use as {type: any | none | tool}.
metadataobjectOptionalAccepted 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.

request
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 — stopend_turn, lengthmax_tokens, tool_callstool_use.

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_startcontent_block_start / content_block_delta (text_delta) / content_block_stopmessage_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.

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: plm_..." \
  -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 (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.