Use Xantly with the Anthropic Python SDK

Xantly ships a byte-accurate /v1/messages endpoint. The official anthropic Python SDK points at it with base_url, messages.create, streaming, tool_use, prompt caching all work.

Xantly ships a byte-accurate /v1/messages endpoint that mirrors Anthropic's Messages API. Point the official anthropic Python SDK at it and everything, messages.create, streaming, tool use, prompt caching, vision, works untouched. No SDK fork needed.

Prerequisites

Setup

from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.xantly.com",
    api_key="xantly_sk_...",
)

Note: base_url is the bare host: no /v1 suffix. The Anthropic SDK appends /v1/messages on its own.

Env-var form:

export ANTHROPIC_BASE_URL=https://api.xantly.com
export ANTHROPIC_API_KEY=xantly_sk_...

messages.create

resp = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a bubble sort in Python."}],
)
print(resp.content[0].text)

You can also use Xantly aliases:

resp = client.messages.create(
    model="xantly/auto-quality",
    max_tokens=1024,
    messages=[...],
)

Streaming

with client.messages.stream(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Stream a haiku."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

Xantly preserves every Anthropic SSE event exactly, message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop. No custom handling.

Tool use (tool_use blocks)

tools = [{
    "name": "get_weather",
    "description": "Get weather for a city.",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

resp = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Weather in Paris?"}],
)

for block in resp.content:
    if block.type == "tool_use":
        print(block.name, block.input)

Prompt caching

Anthropic's prompt caching works as-is. Add the cache_control marker; Xantly forwards it:

resp = client.messages.create(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": "<large context here>",
                "cache_control": {"type": "ephemeral"},
            },
            {"type": "text", "text": "What's the summary?"},
        ],
    }],
    extra_headers={"anthropic-beta": "prompt-caching-2024-07-31"},
)
print(resp.usage.cache_creation_input_tokens)

Xantly's own L1/L2 semantic cache runs in addition to Anthropic's prompt cache, Anthropic caches within a single provider; Xantly caches across providers and sessions.

Async client

import asyncio
from anthropic import AsyncAnthropic

aclient = AsyncAnthropic(
    base_url="https://api.xantly.com",
    api_key="xantly_sk_...",
)

async def main():
    resp = await aclient.messages.create(
        model="xantly/auto-quality",
        max_tokens=512,
        messages=[{"role": "user", "content": "Ping"}],
    )
    print(resp.content[0].text)

asyncio.run(main())

Model choice

Model IDWhen
anthropic/claude-sonnet-4.6Pinned Claude, best default via Messages API.
anthropic/claude-opus-4.5Highest-quality Claude.
anthropic/claude-haiku-4.5Fast + cheap Claude.
xantly/auto-qualityBaRP T1, may pick Claude, GPT, or others, responses translated back into Messages shape.
xantly/auto-valueBalanced.

Verify

resp = client.messages.create(
    model="anthropic/claude-haiku-4.5",
    max_tokens=32,
    messages=[{"role": "user", "content": "say pong"}],
)
print(resp.content[0].text)

Open your Xantly dashboard, the call logs with model, cache status, and USD cost.

What you get

Gotchas

base_url has no /v1. Anthropic's SDK appends the path. Use the bare host, https://api.xantly.com, not https://api.xantly.com/v1.

Anthropic SDK pins API versions via anthropic-version header. Xantly respects the header and forwards to the underlying provider. Default (2023-06-01) works fine.

Streaming with iterators. with client.messages.stream(...) (context manager) is the stable API. Don't use the deprecated stream=True kwarg.

Beta headers. anthropic-beta: prompt-caching-2024-07-31, pdfs-2024-09-25, computer-use-2024-10-22 all pass through.

Next steps