Use Xantly with the Anthropic TypeScript SDK

The @anthropic-ai/sdk npm package accepts a baseURL option. Point it at Xantly's /v1/messages for smart routing, semantic cache, streaming, tool use, and prompt caching.

The @anthropic-ai/sdk npm package takes a baseURL constructor option. Point it at Xantly's /v1/messages endpoint (byte-accurate Anthropic Messages API) and every call, messages.create, streaming, tool use, prompt caching, works untouched.

Prerequisites

Setup

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  baseURL: 'https://api.xantly.com', // bare host, SDK appends /v1/messages
  apiKey: process.env.XANTLY_API_KEY, // xantly_sk_...
})

Env-var style:

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

messages.create

const resp = await client.messages.create({
  model: 'anthropic/claude-sonnet-4.6',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Write a bubble sort in TypeScript.' }],
})
// content is always an array of blocks
const text = resp.content.find((b) => b.type === 'text')?.text ?? ''
console.log(text)

Streaming

const stream = await client.messages.stream({
  model: 'anthropic/claude-sonnet-4.6',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Stream a haiku.' }],
})

for await (const event of stream) {
  if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
    process.stdout.write(event.delta.text)
  }
}

Xantly preserves every Anthropic SSE event exactly.

Tool use

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

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

for (const block of resp.content) {
  if (block.type === 'tool_use') {
    console.log(block.name, block.input)
  }
}

Prompt caching

const resp = await client.messages.create({
  model: 'anthropic/claude-sonnet-4.6',
  max_tokens: 1024,
  messages: [{
    role: 'user',
    content: [
      { type: 'text', text: '<large context>', cache_control: { type: 'ephemeral' } },
      { type: 'text', text: 'Summarize.' },
    ],
  }],
}, {
  headers: { 'anthropic-beta': 'prompt-caching-2024-07-31' },
})

console.log(resp.usage.cache_creation_input_tokens)

Model choice

Model IDWhen
anthropic/claude-sonnet-4.6Pinned Claude, default.
anthropic/claude-opus-4.5Highest-quality Claude.
anthropic/claude-haiku-4.5Fast + cheap Claude.
xantly/auto-qualityBaRP routes T1 pool, translates back to Messages shape.
xantly/auto-valueBalanced.

Verify

const resp = await client.messages.create({
  model: 'anthropic/claude-haiku-4.5',
  max_tokens: 32,
  messages: [{ role: 'user', content: 'say pong' }],
})
console.log(resp.content.find((b) => b.type === 'text')?.text)

What you get

Gotchas

baseURL (camelCase) and no /v1. TypeScript SDK uses baseURL (capital URL). Must be bare host, https://api.xantly.com.

max_tokens is required. Anthropic's Messages API requires it. The SDK will throw if you omit it.

content is always an array. Even for text-only responses. Use resp.content.find((b) => b.type === 'text')?.text.

Streaming API uses messages.stream(...) not messages.create({ stream: true }). The stream helper returns an iterable event stream + exposes finalMessage().

Edge runtime. Works in Cloudflare Workers and Vercel Edge out of the box, the SDK uses fetch.

Next steps