API Reference
Messages
POST /v1/messages — Anthropic Messages API shape, open-weight inference underneath
Anthropic-shaped chat endpoint. Claude Code, the Anthropic SDK, and anything else that speaks this protocol works unmodified against Melious.
Endpoint:
POST /v1/messagesAuth: Bearer token or x-api-key. Requires scope inference.chat.
Header: anthropic-version is optional. Send it if your client already does — Python anthropic 2.x and TypeScript 1.x send 2026-10-21 and the key as Authorization: Bearer; older releases send 2023-06-01 and x-api-key. We accept any version value and both auth headers, and never reject a request for them.
Example
from anthropic import Anthropic
client = Anthropic(
api_key="sk-mel-<YOUR_API_KEY>",
base_url="https://api.melious.ai",
)
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=256,
messages=[{"role": "user", "content": "Name three Hanseatic cities."}],
)
text = "".join(block.text for block in response.content if block.type == "text")
print(text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "sk-mel-<YOUR_API_KEY>",
baseURL: "https://api.melious.ai",
});
const response = await client.messages.create({
model: "claude-sonnet-4",
max_tokens: 256,
messages: [{ role: "user", content: "Name three Hanseatic cities." }],
});
const text = response.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("");
console.log(text);curl https://api.melious.ai/v1/messages \
-H "x-api-key: sk-mel-<YOUR_API_KEY>" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Name three Hanseatic cities."}]
}'Request
| Parameter | Type | Default | Description |
|---|---|---|---|
model | string | — | Anthropic model name or an open-weight model ID. See Model mapping below. |
messages | array | — | Conversation history. Each message has role ("user" or "assistant") and content (string or content blocks). |
max_tokens | integer | — | Required. Maximum tokens to generate. |
system | string | array | none | System prompt — a string, or an array of {"type": "text", "text": "..."} blocks. |
tools | array | none | Anthropic-shape tool definitions with name, description, and input_schema. |
tool_choice | object | auto | {"type": "auto"}, {"type": "any"}, {"type": "tool", "name": "..."}, or {"type": "none"}. A boolean disable_parallel_tool_use on it is forwarded to the provider as the inverse of its parallel-tool-calls flag; non-boolean values are ignored. Forwarded, not enforced. |
stop_sequences | array | none | Custom stop sequences. |
temperature | number | 1 | Sampling temperature, [0, 1]. |
top_p | number | 1 | Nucleus sampling, [0, 1]. |
top_k | integer | — | Forwarded to the provider. Whether the model honors it is up to the provider. |
thinking | object | model default | {"type": "enabled", "budget_tokens": N}, {"type": "adaptive"}, or {"type": "disabled"}. Enabled/adaptive turn reasoning on. Without output_config.effort, the budget picks the effort (≥10000 high, ≥5000 medium, ≥2000 low, below that minimal); with neither, the model default applies. Non-reasoning models ignore it. |
output_config | object | none | format for structured output. effort — exactly low, medium, high, xhigh, or max, lowercase — sets the reasoning depth and wins over thinking.budget_tokens; thinking.type: "disabled" wins over it. Any other value is ignored, not rejected. |
stream | boolean | false | Enable SSE streaming. |
messages[].content blocks
{"type": "text", "text": "..."}{"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "..."}}or{"type": "image", "source": {"type": "url", "url": "..."}}{"type": "tool_use", "id": "...", "name": "...", "input": {...}}(assistant only){"type": "tool_result", "tool_use_id": "...", "content": "..."}(user only)
Model mapping
Anthropic-proprietary names are mapped to open-weight models before inference runs. You can also send open-weight IDs directly.
| You send (contains, case-insensitive) | We run (default) |
|---|---|
opus | the main model |
sonnet | the secondary model |
haiku | the lightweight model |
| anything else | the string as-is |
Which open-weight model fills each slot is shown on melious.ai/hub/models.
The response echoes the original model name back. If you sent claude-sonnet-4, the response model field is claude-sonnet-4 — so clients that hard-check the model name (Claude Code, some middleware) don't break.
We map Anthropic names because Claude Code hardcodes them. For direct control, send an open-weight ID: "model": "<MODEL_ID>" routes to exactly that model with no mapping.
Response
{
"id": "msg_...",
"type": "message",
"role": "assistant",
"content": [
{"type": "text", "text": "Hamburg, Lübeck, Bremen."}
],
"model": "claude-sonnet-4",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 12,
"output_tokens": 7
}
}stop_reason
end_turn— model finished naturally.max_tokens— hit the cap.stop_sequence— matched a custom stop sequence.tool_use— model wants to call a tool.
content blocks
Same shapes as input: text, tool_use, plus thinking on reasoning models ({"type": "thinking", "thinking": "...", "signature": ""}), always before the text. tool_use blocks include id, name, and parsed input. A reasoning model that uses its whole max_tokens on thinking returns a thinking block and no text block.
No environment_impact or billing_cost
Messages responses carry neither Melious extension, streamed or not. Requests are still metered and billed; among conversational endpoints, Chat completions returns both fields. See Environmental impact.
Streaming
stream: true switches the response to Server-Sent Events with Anthropic event types:
| Event | Meaning |
|---|---|
message_start | Beginning of the message — includes initial usage. |
content_block_start | New content block (thinking, text, or tool_use) begins. |
content_block_delta | Token(s) appended to the current block: thinking_delta, text_delta, or input_json_delta for tool arguments. |
content_block_stop | Current block ended. |
message_delta | Final metadata (stop_reason, usage deltas). |
message_stop | Stream ended. |
error | Mid-stream failure. Always the last event; nothing from the success tail comes before it. |
Streams don't send environment_impact or billing_cost either — message_delta carries stop_reason and token counts, nothing more — and a stream that ends early can still be charged for what was generated.
See Streaming for consumer code.
What's supported vs not
Supported: system prompts, multi-turn conversation, tool use with tool_use/tool_result blocks, streaming, vision (if the underlying model supports it), stop sequences, temperature/top_p/top_k, max_tokens.
Extended thinking works on reasoning models. Send thinking: {"type": "enabled", "budget_tokens": 8000} to turn it on, {"type": "disabled"} to turn it off, or leave it out for the model's default. Thinking comes back as a thinking content block before the text (signature is always an empty string — don't send it back expecting Anthropic to verify it). Read the first text block, not content[0].
To pick the depth directly, send output_config: {"effort": "high"} — low, medium, high, xhigh, or max, lowercase. Effort wins over the budget, disabled wins over both, and enabled or adaptive with neither follows the model default. xhigh and max reach the model as sent only on glm-5.3 and glm-5.3-flash; everywhere else they fold to high.
Not supported today:
- Anthropic prompt caching (
cache_controlblocks) — silently dropped; there's no explicit cache handle to set. Transparent prefix caching still happens on the provider side and shows up asusage.cache_read_input_tokensin the response, billed at the cheaper cache-read rate. - Fine-grained tool-streaming signals beyond
content_block_delta/input_json_delta.
If your client depends on one of these, open an issue — we'll scope the work.
Errors
Errors use the Anthropic shape:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "..."
},
"request_id": "..."
}request_id is the same id as the request-id and x-request-id response headers — every Messages response carries both — and it's what the Anthropic SDKs expose as _request_id and APIStatusError.request_id. Quote it to support.
Use error.type to classify the error and check error.code when present. These are endpoint-level errors. Account-level throttling uses a flat 429 with code RATE_LIMIT_EXCEEDED; see Errors. Common statuses:
| Status | type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed request, bad content block, capability or context mismatch. |
| 401 | authentication_error | Missing or invalid API key. |
| 403 | permission_error | Billing refusal, or the key can't call this endpoint. |
| 404 | not_found_error | Model not found. |
| 429 | rate_limit_error | Throttled — back off per Retry-After. |
| 503 | api_error | Transient — retry with backoff. |
Mid-stream failures keep the HTTP 200 and arrive as an error SSE event — the last event on the stream: {"type": "error", "error": {"type": "api_error", "message": "..."}}. A code is included when one applies; the event carries no request_id, so read the request-id header instead.
Nothing from the success tail comes before it: no content_block_stop for the open block, no tool_use block, no message_delta, no message_stop. A failure before any output skips message_start too — only ping events may come first. Treat a stream that ends with neither message_stop nor error as failed — it was cut off abnormally. See Streaming for retry boundaries and billing.
Full tables and retry guidance: Errors.
Related
Count tokens for preflight sizing • From Anthropic for setup • Tool calling for the loop.