Anthropic Messages
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 is the reference for every parameter.
Set up the SDK#
The Anthropic SDK sends the key in x-api-key. Authorization: Bearer works too.
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."}]
}'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 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.
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)The gateway holds the stream until the first token, so a channel that fails early is replaced without you noticing. Details are in 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 for the rules of each model.
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)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.
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)Other tool options, such as tool_choice and strict, pass through unchanged. Anthropic's tool use guide 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.
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)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.
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.
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"}]}'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 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 when your key is not bound to one. |
x-session-id | Optional. Groups requests into one conversation for cache-aware routing. 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.