← back to blog

MonoRouter API Compatibility: What Works and What Doesn't

A complete reference of which Anthropic API features MonoRouter supports, with workarounds for the few that don't apply.

MonoRouter aims to be a drop-in replacement for the Anthropic API. In practice, that means supporting the Messages API surface that developer tools actually use. Here's the complete compatibility picture — with code examples for every major feature.

Compatibility Matrix

FeatureStatusNotes
Messages API (create)✅ FullAll content types, roles, system prompts
Streaming (SSE)✅ FullAll event types forwarded in real time
Tool use✅ FullDefinitions, tool_use, tool_result, parallel calls
Model selection✅ FullOpus, Sonnet, Haiku via model parameter
Temperature / top_p✅ FullSampling params pass through unchanged
Max tokens✅ FullRespects your specified limit
Stop sequences✅ FullCustom stop sequences work as expected
System prompts✅ FullSingle and multi-block
Image inputs✅ FullBase64 and URL content blocks
Extended thinking⚠️ CaveatWorks — set client timeout ≥ 300s
Token counting⚠️ CaveatAccurate, but from subscription session
Rate limit headers⚠️ CaveatReflects MonoRouter pool, not Anthropic API
Batches API❌ N/ADesigned for API-credit bulk processing
Admin API❌ N/AOrganizational management for API accounts

Messages API

The core endpoint works identically. Here's a request and response through MonoRouter:

import anthropic

client = anthropic.Anthropic(
    api_key="mr_live_abc123",
    base_url="https://api.monorouter.dev/v1",
)

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=256,
    system="You are a helpful coding assistant.",
    messages=[
        {"role": "user", "content": "What does `**kwargs` do in Python?"}
    ],
)

print(response.content[0].text)
print(f"Usage: {response.usage.input_tokens} in / {response.usage.output_tokens} out")

The response object has the same shape as the direct API — id, type, role, content, model, stop_reason, and usage fields are all present and accurate.

Streaming

Full SSE streaming with all event types:

with client.messages.stream(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain monads in 100 words"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

# Event types forwarded:
#   message_start → content_block_start → content_block_delta (×N)
#   → content_block_stop → message_delta → message_stop

The raw SSE frames are identical to what the Anthropic API produces. MonoRouter doesn't buffer, batch, or rewrite events.

Tool Use

Full tool use protocol — definitions, responses, results, and parallel calls:

tools = [{
    "name": "lookup_user",
    "description": "Look up a user by email",
    "input_schema": {
        "type": "object",
        "properties": {
            "email": {"type": "string", "description": "User email address"}
        },
        "required": ["email"]
    }
}]

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Find the user with email [email protected]"}],
)

# response.stop_reason == "tool_use"
# response.content[0].type == "tool_use"
# response.content[0].name == "lookup_user"
# response.content[0].input == {"email": "[email protected]"}

Every field — tool_use_id, name, input, is_error — passes through MonoRouter unchanged.

Image Inputs

Base64 and URL image content blocks both work:

import base64

with open("screenshot.png", "rb") as f:
    image_data = base64.standard_b64encode(f.read()).decode("utf-8")

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/png",
                    "data": image_data,
                },
            },
            {
                "type": "text",
                "text": "What's in this screenshot?"
            }
        ],
    }],
)

Multi-Block System Prompts

Complex system prompts with multiple content blocks are supported:

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system=[
        {"type": "text", "text": "You are a code reviewer for a Python project."},
        {"type": "text", "text": "Focus on: security issues, performance problems, and code style."},
        {"type": "text", "text": "Always cite the specific line numbers in your feedback."},
    ],
    messages=[{"role": "user", "content": diff_text}],
)

SDK Compatibility

Every official Anthropic SDK supports custom base URLs:

Python:

client = anthropic.Anthropic(
    base_url="https://api.monorouter.dev/v1"
)
# or via env: ANTHROPIC_BASE_URL

TypeScript:

const client = new Anthropic({
    baseURL: "https://api.monorouter.dev/v1"
});
// or via env: ANTHROPIC_BASE_URL

cURL:

curl https://api.monorouter.dev/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: mr_live_abc123" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-4-20250514",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "Hello"}]
  }'

No forks, no patches, no custom clients. The official SDKs work as-is — just point them at MonoRouter.

Extended Thinking

Extended thinking works but requires generous timeouts. Claude's internal reasoning can take 60–120 seconds for complex problems:

client = anthropic.Anthropic(
    base_url="https://api.monorouter.dev/v1",
    timeout=300.0,  # 5 minutes — important for extended thinking
)

response = client.messages.create(
    model="claude-opus-4-20250514",
    max_tokens=16384,
    thinking={
        "type": "enabled",
        "budget_tokens": 10000,
    },
    messages=[{"role": "user", "content": "Prove that the square root of 2 is irrational."}],
)

for block in response.content:
    if block.type == "thinking":
        print(f"[Thinking]: {block.thinking[:200]}...")
    elif block.type == "text":
        print(f"\n{block.text}")

What's Not Supported

These Anthropic API features don't apply in the MonoRouter model:

  • Batches API — designed for bulk processing with API credits. Not relevant when routing through subscriptions.
  • Admin API — organizational management for Anthropic API accounts. MonoRouter has its own dashboard.
  • Usage-based billing metadata — there's nothing to bill. You're using your subscription.
  • API key management — your MonoRouter key is separate from Anthropic API keys.

None of these affect typical developer tooling. The features that Claude Code, Cursor, LangChain, and custom agents actually use — messages, streaming, tools — are fully supported.