---
title: Anthropic Messages
description: Call Claude through the Anthropic Messages API with the official SDKs. Streaming, extended thinking, tool use, prompt caching and token counting work as documented by Anthropic.
---

Lovely Router serves `POST /v1/messages` and `POST /v1/messages/count_tokens` exactly as Anthropic defines them. Keep your existing code, change the base URL to `https://lovelyrouter.com`, and use your Lovely Router key. Request bodies pass through unchanged, so [Anthropic's documentation](https://platform.claude.com/docs/en/api/messages) is the reference for every parameter.

## Set up the SDK

The Anthropic SDK sends the key in `x-api-key`. `Authorization: Bearer` works too.

```bash title="cURL"
curl https://lovelyrouter.com/v1/messages \
  -H "x-api-key: $LOVELY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5.5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Explain HTTP caching in two sentences."}]
  }'
```

```python title="Python"
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://lovelyrouter.com",
    api_key=os.environ["LOVELY_API_KEY"],
)
message = client.messages.create(
    model="claude-sonnet-5.5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain HTTP caching in two sentences."}],
)
print(message.content[0].text)
```

```js title="Node.js"
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  baseURL: 'https://lovelyrouter.com',
  apiKey: process.env.LOVELY_API_KEY,
});
const message = await client.messages.create({
  model: 'claude-sonnet-5.5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Explain HTTP caching in two sentences.' }],
});
console.log(message.content[0].text);
```

The SDKs also read `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY` from the environment, so `export ANTHROPIC_BASE_URL=https://lovelyrouter.com` is enough for code that builds a default client.

Claude models available today: `claude-fable-5.1`, `claude-opus-5.5`, `claude-sonnet-5.5`, `claude-haiku-5.5`, and earlier versions. [`GET /v1/models`](/docs/models/pricing) has the current list with prices.

## Stream responses

Set `stream: true`, or use the SDK's stream helper. You receive the standard event sequence: `message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta`, `message_stop`.

```python title="Python"
with client.messages.stream(
    model="claude-sonnet-5.5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "Write a limerick about latency."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()
print("\n", final.usage)
```

```js title="Node.js"
const stream = client.messages.stream({
  model: 'claude-sonnet-5.5',
  max_tokens: 2048,
  messages: [{ role: 'user', content: 'Write a limerick about latency.' }],
});
stream.on('text', (text) => process.stdout.write(text));
const final = await stream.finalMessage();
console.log('\n', final.usage);
```

The gateway holds the stream until the first token, so a channel that fails early is replaced without you noticing. Details are in [Streaming](/docs/models/streaming).

## Use extended thinking

Send `thinking` and `output_config` as Anthropic documents them for the model you call. Current models use adaptive thinking with an effort level; older models (Claude 4.5 and earlier) take a fixed `budget_tokens`. Check [Anthropic's thinking guide](https://platform.claude.com/docs/en/build-with-claude/extended-thinking) for the rules of each model.

```python title="Python"
message = client.messages.create(
    model="claude-opus-5.5",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    output_config={"effort": "high"},
    messages=[{"role": "user", "content": "How many weekdays are there in 2027?"}],
)
for block in message.content:
    if block.type == "thinking":
        print("Thinking:", block.thinking)
    elif block.type == "text":
        print("Answer:", block.text)
```

```js title="Node.js"
const message = await client.messages.create({
  model: 'claude-opus-5.5',
  max_tokens: 16000,
  thinking: { type: 'adaptive', display: 'summarized' },
  output_config: { effort: 'high' },
  messages: [{ role: 'user', content: 'How many weekdays are there in 2027?' }],
});
for (const block of message.content) {
  if (block.type === 'thinking') console.log('Thinking:', block.thinking);
  else if (block.type === 'text') console.log('Answer:', block.text);
}
```

Thinking tokens are part of `usage.output_tokens` and are billed at the output rate. Pass thinking blocks back unchanged when you continue a conversation with tools.

## Use tools

Define tools with a JSON Schema `input_schema`. When the response has `stop_reason: "tool_use"`, run the tool and send the result back in a `tool_result` block.

```python title="Python"
tools = [{
    "name": "get_weather",
    "description": "Current weather for a city.",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]
messages = [{"role": "user", "content": "Do I need an umbrella in Lisbon?"}]

response = client.messages.create(
    model="claude-sonnet-5.5", max_tokens=1024, tools=tools, messages=messages,
)
if response.stop_reason == "tool_use":
    call = next(b for b in response.content if b.type == "tool_use")
    result = {"city": call.input["city"], "forecast": "light rain"}  # your code here
    messages += [
        {"role": "assistant", "content": response.content},
        {"role": "user", "content": [
            {"type": "tool_result", "tool_use_id": call.id, "content": str(result)},
        ]},
    ]
    response = client.messages.create(
        model="claude-sonnet-5.5", max_tokens=1024, tools=tools, messages=messages,
    )
print(response.content[0].text)
```

```js title="Node.js"
const tools = [{
  name: 'get_weather',
  description: 'Current weather for a city.',
  input_schema: {
    type: 'object',
    properties: { city: { type: 'string' } },
    required: ['city'],
  },
}];
const messages = [{ role: 'user', content: 'Do I need an umbrella in Lisbon?' }];

let response = await client.messages.create({
  model: 'claude-sonnet-5.5', max_tokens: 1024, tools, messages,
});
if (response.stop_reason === 'tool_use') {
  const call = response.content.find((b) => b.type === 'tool_use');
  const result = { city: call.input.city, forecast: 'light rain' }; // your code here
  messages.push(
    { role: 'assistant', content: response.content },
    { role: 'user', content: [{ type: 'tool_result', tool_use_id: call.id, content: JSON.stringify(result) }] },
  );
  response = await client.messages.create({
    model: 'claude-sonnet-5.5', max_tokens: 1024, tools, messages,
  });
}
console.log(response.content[0].text);
```

Other tool options, such as `tool_choice` and `strict`, pass through unchanged. [Anthropic's tool use guide](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview) covers them.

## Cache long prompts

Mark the end of the stable part of your prompt with `cache_control`. The first request writes the cache; later requests that start with the same prefix read it at a fraction of the input price.

```python title="Python"
response = client.messages.create(
    model="claude-sonnet-5.5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": LONG_REFERENCE_DOCUMENT,
        "cache_control": {"type": "ephemeral"},
    }],
    messages=[{"role": "user", "content": "Summarize section 3."}],
)
u = response.usage
print(u.cache_creation_input_tokens, u.cache_read_input_tokens, u.input_tokens)
```

```js title="Node.js"
const response = await client.messages.create({
  model: 'claude-sonnet-5.5',
  max_tokens: 1024,
  system: [{
    type: 'text',
    text: LONG_REFERENCE_DOCUMENT,
    cache_control: { type: 'ephemeral' },
  }],
  messages: [{ role: 'user', content: 'Summarize section 3.' }],
});
const u = response.usage;
console.log(u.cache_creation_input_tokens, u.cache_read_input_tokens, u.input_tokens);
```

For `claude-sonnet-5.5`, cache writes cost $2.50 and cache reads $0.10 per million tokens, against $2.00 for uncached input. Prefixes shorter than the model's minimum are not cached and report zero for both fields. The gateway keeps each conversation on the channel that holds its cache; see [Prompt caching](/docs/models/caching).

## Count tokens

`POST /v1/messages/count_tokens` takes the same body as a message request, minus `max_tokens`, and returns `input_tokens`. It is free and works with a zero balance.

```bash title="cURL"
curl https://lovelyrouter.com/v1/messages/count_tokens \
  -H "x-api-key: $LOVELY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-5.5",
       "messages": [{"role": "user", "content": "Hello, Claude"}]}'
```

```python title="Python"
count = client.messages.count_tokens(
    model="claude-sonnet-5.5",
    messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(count.input_tokens)
```

```js title="Node.js"
const count = await client.messages.countTokens({
  model: 'claude-sonnet-5.5',
  messages: [{ role: 'user', content: 'Hello, Claude' }],
});
console.log(count.input_tokens);
```

> [!NOTE] Counts are approximate
> Use `count_tokens` to check a prompt against the context window or to budget. The charge for a call always comes from the `usage` the model reports in its response.

## Use Claude Code

Claude Code speaks this API. Set `ANTHROPIC_BASE_URL=https://lovelyrouter.com` and `ANTHROPIC_AUTH_TOKEN` to your key, and every session stays on one channel so its prompt cache keeps working. [Use with your tools](/docs/integrations) has the full setup, including model settings.

## Headers

| Header | Notes |
| --- | --- |
| `x-api-key` or `Authorization: Bearer` | Your Lovely Router key. Required. |
| `anthropic-version` | Forwarded. Defaults to `2023-06-01` when omitted. |
| `anthropic-beta` | Forwarded as sent. |
| `x-lovely-group` | Optional. Pins the request to a [channel group](/docs/models/channel-groups) when your key is not bound to one. |
| `x-session-id` | Optional. Groups requests into one conversation for [cache-aware routing](/docs/models/caching#send-an-explicit-session-id). `metadata.user_id` in the body does the same. |

Other request headers are not forwarded. Errors use Anthropic's shape, `{"type": "error", "error": {"type": "...", "message": "..."}}`; see [Errors](/docs/api/errors).
