Melious

Melious implements Anthropic's Messages API (POST /v1/messages) alongside the OpenAI API. If you're using Claude Code, the Anthropic SDK, or anything that speaks that shape, you don't need a new client — just two environment variables.

Claude Code

Claude Code needs two environment variables and, if you want to choose the models per slot, five more. Claude Code has the full setup, including the .claude/settings.local.json form.

The base URL is https://api.melious.ai — not .../v1. Anthropic's SDK appends its own version prefix, so you give it the root.

Anthropic SDK

Same idea, different caller:

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

Any current SDK release works. Python anthropic 2.x and TypeScript 1.x send anthropic-version: 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 headers. The SDKs read our request-id response header into _request_id and APIStatusError.request_id — that's the id to quote to support.

How model names are mapped

We don't run Claude. When you send a claude-* model ID, we map it to an open-weight model before the inference runs. The Anthropic SDK hardcodes these names, and Claude Code ships with them — so the mapping is what lets those clients work unmodified.

The defaults:

You sendWe run
anything containing opus (case-insensitive)the main model
anything containing sonnetthe secondary model
anything containing haikuthe lightweight model
anything elsethe string as-is

Which open-weight model fills each slot is shown on melious.ai/hub/models.

The response carries the original name back. If you sent claude-sonnet-4, the response model field is claude-sonnet-4 — so Claude Code's internal assumptions don't break.

To run a specific open-weight model directly, send its ID:

response = client.messages.create(
    model="<MODEL_ID>",   # no mapping — runs exactly this model
    max_tokens=256,
    messages=[...],
)

What's supported

Everything Anthropic SDK clients rely on for normal chat work:

  • System prompts — system as string or content blocks
  • Multi-turn messages — the usual role: "user" | "assistant" alternation
  • Tool use — Anthropic-shape tools + tool_choice, with tool_use / tool_result blocks in the response
  • Streaming — SSE with Anthropic event types (message_start, content_block_delta, message_stop)
  • Vision — image content blocks with base64 or URL sources (model has to support it)
  • Stop sequences, temperature, top_p, top_k, max_tokens — passed through. Whether a model honors top_k is up to the provider.
  • 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. output_config: {"effort": "high"} (low, medium, high, xhigh, or max, lowercase) picks the depth directly and wins over the budget; disabled wins over both. xhigh and max reach the model as sent only on glm-5.3 and glm-5.3-flash; elsewhere they fold to high. 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].

Token counting is also implemented:

counted = client.messages.count_tokens(
    model="claude-sonnet-4",
    messages=[{"role": "user", "content": "Hello"}],
)
print(counted.input_tokens)

See the Messages and Count tokens reference pages for full field details.

What's not supported

Honest list:

  • Explicit cache_control blocks aren't exposed — there's no handle to mark a prefix as cacheable. Transparent prefix caching still happens on the provider side and shows up as usage.cache_read_input_tokens, billed at the cheaper cache-read rate. See Models.
  • Fine-grained tool streaming events — we emit content_block_delta for text and input_json_delta for tool arguments, matching the SDK's parser; less common events may be absent.

The intent is: if Claude Code and the stock Anthropic SDK work, we call it supported. If you're doing something exotic and something's missing, open an issue.

When it breaks

  • 404 on every request — ANTHROPIC_BASE_URL ends in /v1. The SDK appends /v1/messages itself, so the request goes to /v1/v1/messages, which doesn't exist. Set the base URL to https://api.melious.ai.
  • Tool loops behave differently than with Claude — this is a model-capability thing, not a protocol thing. Stronger models handle longer tool sequences better than smaller ones.

Error codes and retry guidance: Errors.

On this page