Messages
Messages is Anthropic's API. llm.buzz serves it at POST https://api.llm.buzz/api/v1/messages for every text model, Claude or not, so the Anthropic SDK and the tools built on it work with the base URL alone.
Send a request
Point the Anthropic SDK at https://api.llm.buzz/api, without /v1: the SDK adds /v1/messages itself. max_tokens is required. Claude Code takes the same base URL: Authentication.
from anthropic import Anthropic
import os
client = Anthropic(
base_url="https://api.llm.buzz/api",
api_key=os.environ["LLM_BUZZ_KEY"],
)
message = client.messages.create(
model="anthropic/claude-sonnet-5.5",
max_tokens=2048,
system="You answer in one short sentence.",
messages=[{"role": "user", "content": "Why do HTTP clients retry with backoff?"}],
)
for block in message.content:
if block.type == "text":
print(block.text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.llm.buzz/api",
apiKey: process.env.LLM_BUZZ_KEY,
});
const message = await client.messages.create({
model: "anthropic/claude-sonnet-5.5",
max_tokens: 2048,
system: "You answer in one short sentence.",
messages: [{ role: "user", content: "Why do HTTP clients retry with backoff?" }],
});
for (const block of message.content) {
if (block.type === "text") console.log(block.text);
}curl https://api.llm.buzz/api/v1/messages \
-H "x-api-key: $LLM_BUZZ_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5.5",
"max_tokens": 2048,
"system": "You answer in one short sentence.",
"messages": [{"role": "user", "content": "Why do HTTP clients retry with backoff?"}]
}'The loop reads the text blocks because a model that thinks puts a thinking block first. The fields llm.buzz adds to Anthropic's request, provider, gateway and session_id, go through extra_body in the SDKs, for example extra_body={"provider": {"data_collection": "deny"}}. The model name, and the models you can call, come from GET https://api.llm.buzz/api/v1/models (Choosing an API).
Request fields
A field Messages does not know, such as seed, is dropped, and the body's x-sgr-ignored-parameters names it, on message_start and on the final message. A field llm.buzz deliberately refuses is a 400 before any provider is called, in Anthropic's invalid_request_error.
| Field | What llm.buzz does |
|---|---|
model | The model's name, author/slug. :floor tries the cheapest provider first and :nitro the fastest, and :exacto is accepted and ignored. :online is accepted and searches nothing, and x-sgr-ignored-parameters names it model:online. Models and routing. |
max_tokens | Required. The most output tokens, thinking included. A provider that needs a larger minimum than your limit is skipped, and when none is left the request is refused. |
system | The system instruction: a string, or a list of text blocks. |
messages | The conversation, with the roles user and assistant. A block is text; image, from base64 or a URL; document, only for a model that reads PDFs, which no model on llm.buzz does today; tool_use; tool_result, whose content may hold text and images; or thinking and redacted_thinking, sent back unchanged. A model that cannot read an attachment refuses the request before any provider is called. Images in requests. |
stop_sequences | Sent to providers that take them. Where a provider has no stop field, llm.buzz cuts the answer at the first match itself and ends it with stop_reason: stop_sequence, the matched string in stop_sequence. |
temperature, top_p, top_k | Sent to the provider. A model that takes none has the value dropped and named: Sampling settings a model does not take. |
stream | true streams the answer as server-sent events: Stream. |
tools, tool_choice | Sent, in Anthropic's shape. Some models cannot be forced to call a tool, and some providers take a tool schema only in part. Tools and structured output says what happens to each. |
thinking, reasoning | How hard the model thinks, and whether you see it. Thinking. |
output_config | output_config.effort sets the reasoning effort, and every provider receives it in its own terms: Thinking. output_config.format asks for structured output. An output_config that holds more than effort, such as a format, is sent as you wrote it when every provider that may serve the request takes Anthropic's API. Otherwise llm.buzz drops the object and names output_config in x-sgr-ignored-parameters, and its effort still applies. Tools and structured output. |
cache_control | Anthropic's prompt-cache marker, taken as the Messages API defines it, at every place it allows, the top level included. Passed on or translated, never added by llm.buzz. Prompt caching. |
context_management | Sent where the provider takes it, with the anthropic-beta token it needs, and dropped and named elsewhere. |
inference_geo, speed, diagnostics | Sent where the provider takes them, and dropped and named elsewhere. |
session_id | A name for the conversation, 1 to 256 characters, so its turns stay on the provider that holds its cache. An empty or whitespace-only value counts as absent; any other value that is not a string of 1 to 256 characters, such as one over 256 characters, is a 400 naming session_id. The x-session-id header names it too. |
provider | Steers one request: order, only, ignore, allow_fallbacks, sort, zdr and data_collection. Other keys, such as require_parameters, quantizations, max_price and sort.partition, are accepted and do not change which provider serves. The provider object. |
gateway | llm.buzz's own per-request bounds, retry and routing.allow_fallbacks. Never sent to a provider. Retries and fallbacks per request. |
plugins, web_search_options | Dropped whole and named: the whole plugins field is named plugins, not plugins.<id> as on Chat Completions and Responses. llm.buzz runs no web search, so nothing is searched. |
models, route, transforms, usage, trace, debug | Dropped and named. A model's own providers fail over through its routing preset, in place of models. Usage counts and usage.cost come back whether or not you send usage. |
seed, logit_bias, response_format | Dropped and named. A Chat response_format is not applied on Messages: ask for structured output with output_config.format. |
defer_loading on a tool | Keeps the tool out of the prompt until a tool search finds it. Tool search. |
Refused fields, including betas, container, fallbacks, fallback_credit_token, mcp_servers, service_tier, thread and user_profile_id | 400 invalid_request_error naming the field. Send the anthropic-beta header in place of betas. |
Thinking
You can ask for thinking three ways, and llm.buzz translates each into the terms of the provider that serves the request.
thinkingtakes Anthropic's forms:adaptive,disabled, andenabledwith abudget_tokens. Every Claude model llm.buzz lists today is in an adaptive-only family, and those families refuse a budget. Athinkingobject withbudget_tokensanswers 400invalid_request_errornamingthinking.budget_tokens, and a bare{"type": "enabled"}runs adaptive thinking, namedthinking.type->adaptiveinx-sgr-ignored-parameters. Useadaptive, and set the depth withoutput_config.effort.- A model from another provider gets
thinkingtranslated. A bareenabledoradaptivereasons at the model's default effort, and at medium when the model names none. Abudget_tokensgoes to providers that have a budget field, and is refused before any provider is called where none has one. - For a Claude model that takes a budget, which none on llm.buzz does today, a bare
{"type": "enabled"}takes half of the output limit as its budget, andbudget_tokensmust be belowmax_tokens(at or above it is a 400 namingthinking.budget_tokens). reasoning, OpenRouter's object, is accepted too:effortormax_tokens, plusenabledandexclude. Its fields must agree: an effort tier or a budget besideenabled: false, oreffort: "none"besideenabled: true, is a 400invalid_request_errorwhose message ends(param: reasoning.enabled).output_config.effortis a reasoning control that every provider receives in its own terms. A route that cannot serve the effort refuses the request and namesoutput_config.effort.
Thinking off is not always available. thinking: {"type": "disabled"} is refused before any provider is called for Claude Opus 5.5, Sonnet 5.5, Fable 5 and Fable 5.1, at every effort level. Leave thinking out, or use adaptive and choose an effort. Sonnet 5.5 takes {"type": "between_tools"} in place of disabled: it turns off up-front thinking, works with an output_config.effort of low, medium or high, and takes no other thinking field. reasoning.exclude: true keeps reasoning text out of the answer while the model still thinks and bills for it, but Anthropic's own signed thinking blocks stay, because replaying them depends on them.
A conversation can move between models. Claude's signed thinking blocks go back to Claude. Another provider gets the turn without them, and x-sgr-ignored-parameters names it messages.thinking->dropped(unsupported_by_provider).
A max_tokens under 1,024, with no thinking or effort setting of your own, on a model whose providers all reason by default and offer a none effort, is sent at effort none, named reasoning_effort->none(max_tokens_headroom). The reply is text, not thinking cut off at the ceiling. Reasoning has the rest.
Headers
You send:
| Header | Use |
|---|---|
x-api-key or Authorization | Your inference key. The Anthropic SDK sends x-api-key; Authorization: Bearer carries the same key. Authentication. |
anthropic-version | The SDK sends it, and llm.buzz accepts it. |
anthropic-beta | Anthropic's beta tokens. A token llm.buzz does not pass on is dropped, and x-sgr-ignored-parameters names it anthropic-beta.<token>; the request still serves. |
x-session-id | Names the conversation, as session_id does. |
X-Title, HTTP-Referer | Name your app. App attribution. |
Messages has no Idempotency-Key: see Retries. Every answer carries:
| Header | Value |
|---|---|
x-request-id | The request's ID. Its generation ID is gen- and the same 32 hex digits: Cost of a finished request. |
x-gateway-alias | The model name that served. |
x-gateway-canonical-model | The exact model that produced the answer. |
x-gateway-provider | The display name of the provider that answered, llm.buzz for llm.buzz's own provider. The message has no provider key, so read it here. |
x-gateway-route-depth, x-gateway-route-reason | The answering provider's position in the order tried, and why the route was chosen. |
An answer that asks you to wait, a 429 and some 402 and 503 answers, carries Retry-After: wait that many seconds before you retry.
Count tokens
POST https://api.llm.buzz/api/v1/messages/count_tokens takes a Messages request body and answers how many input tokens it comes to. It needs no max_tokens; one is accepted and changes nothing. gateway is refused with a 400 invalid_request_error naming it.
curl https://api.llm.buzz/api/v1/messages/count_tokens \
-H "x-api-key: $LLM_BUZZ_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5.5",
"messages": [{"role": "user", "content": "Why do HTTP clients retry with backoff?"}]
}'The answer is {"input_tokens": N}. The count is llm.buzz's own estimate, because no provider counts tokens for it, and the body says so with input_tokens->estimated(gateway_tokenizer) in x-sgr-ignored-parameters. It is free, and a key whose spending limit is reached can still call it. It answers the same 404 as a Messages call for a model none of whose providers meets your data policy (Models and routing).
The answer
A non-streamed answer is Anthropic's message object. llm.buzz adds or fixes these parts:
contentholdstextblocks,tool_useblocks, and the model's reasoning asthinkingblocks: Anthropic's own, signed, when Anthropic serves the request, and one unsigned block with the readable reasoning from other providers. A tool turn of some models also ends with aredacted_thinkingblock. Send every thinking block back unchanged with the assistant turn.reasoning.excludewithholds the unsigned one.stop_reasonisend_turnfor a finished answer,tool_usewhen the model called tools,max_tokenswhen the output ceiling cut it, andstop_sequencewhen a stop sequence you sent matched, with the matched string instop_sequence.usageis described below.x-sgr-ignored-parametersis a key in the body, not a header. It lists every field llm.buzz dropped and every value it changed, with the reason, such astemperature->dropped(unsupported_by_provider). It is on the final message and onmessage_start. Check it when a setting seems to have no effect.
Stream
Set stream: true and the answer arrives as Anthropic's events.
| Event | What it carries |
|---|---|
message_start, then one ping | The message so far, with usage and x-sgr-ignored-parameters. |
content_block_start, content_block_delta, content_block_stop | The content blocks: text, tool input, thinking. |
message_delta | The mapped stop_reason, the final usage and the cost. |
message_stop | The end of a finished answer. |
error | One last event when a failure follows the first output, in place of message_stop. A failure before the stream opens is an ordinary HTTP error. |
Closing a Messages stream stops the request, and the prompt and the output produced up to then are billed. While the model is silent, comment lines beginning with : keep the connection active; clients skip them. Streaming covers the event shapes of all three text APIs.
Usage and cost
| Field | Value |
|---|---|
input_tokens | Uncached input tokens only. |
cache_creation_input_tokens | Input tokens written to the provider's cache. |
cache_read_input_tokens | Input tokens read from it. |
output_tokens | Output tokens. |
cache_creation | Anthropic's split of the writes, ephemeral_5m_input_tokens and ephemeral_1h_input_tokens, present when the split is known. |
cost | The request's total cost in US dollars at your rate. On the final message and on message_delta, never on message_start. |
The four counts are always present, 0 when the provider reported none. The usage of message_start is a figure taken before the provider answers: Anthropic's own start counts when Anthropic serves the request, and llm.buzz's estimate of the prompt for any other provider, with output_tokens of 1 and cache counts of 0. message_delta carries the final counts, which replace it.
The Anthropic Python SDK does not copy cost into the message it assembles from a stream. Read it from the message_delta event, or from the answer of a request that does not stream:
stream = client.messages.create(
model="anthropic/claude-sonnet-5.5",
max_tokens=2048,
messages=[{"role": "user", "content": "Why do HTTP clients retry with backoff?"}],
stream=True,
)
for event in stream:
if event.type == "message_delta":
print(event.usage.model_extra.get("cost"))message.usage.model_extra["cost"] is the same value on a non-streamed message. cost is a JSON number that includes any billed tool-search rounds, so it can exceed the counts times the listed prices. A failed attempt that another provider replaced adds 0. It is left out when any part of the cost is unknown; a model with no price reports 0. Usage and limits covers the rest, and Prompt caching the cache fields.
Retries
The Messages API defines no idempotency header, so llm.buzz applies none: a retry is a separate request that runs, and bills, again.
- The Anthropic SDK also retries failed requests on its own, and each retry is a separate request. Set
max_retries=0when your application must bound the work of one operation. - A provider that fails or times out before output reaches you is replaced by the next one, and the failed attempt costs nothing; a provider's own 400 is not tried on another provider. Once output has reached you, the request stays on the provider that sent it.
Retries and idempotency explains the rest.
Errors
Errors come in Anthropic's body, {"type": "error", "error": {"type": "...", "message": "..."}}, and the message says what to do. A named field is appended to the message as (param: NAME), and a refusal by the model adds refusal_reason to error. error.type follows the status:
| Status | error.type | When |
|---|---|---|
| 400 | invalid_request_error | You sent a refused field, such as service_tier; a field is missing or fails validation, max_tokens included; thinking is off on a model that cannot turn it off; a budget_tokens on a model that takes none; a route cannot serve output_config.effort; or the prompt fits no provider's context window. The message names the field and says what to change. |
| 401 | authentication_error | The key is missing, unknown, expired, revoked or disabled. |
| 402 | billing_error | Your balance is spent and the model is not free, or requests already running hold what it leaves. |
| 403 | permission_error | The key's spending limit is reached, or a management key called Messages. |
| 404 | not_found_error | The model is unknown, unavailable, has no provider that meets your data policy, or has no EU provider for an organization whose requests stay in the EU. |
| 413 | request_too_large | The body is over 64 MiB. |
| 429 | rate_limit_error | A provider is throttling, a route is unavailable, or llm.buzz is overloaded. Wait Retry-After seconds, then retry. |
| 503 | overloaded_error | Every provider is down, or llm.buzz is restarting. Retry with backoff, and after Retry-After seconds when the answer carries it. |
| 500, 502, 504 | api_error | llm.buzz or a provider failed, or the request ran past 600 seconds: retry with backoff. A 502 can also mean a provider's answer was over llm.buzz's response limit: ask for less output. |
A stream that fails after output ends with an error event, described under Stream. Errors lists every code, including the key, balance, model and provider errors this API shares with the others.
Limits
max_tokensis required. A model's context window iscontext_lengthon its entry inGET https://api.llm.buzz/api/v1/models, andtop_provider.max_completion_tokensis its output ceiling.- A request may run for 600 seconds. A stream still generating then ends with a timeout error and is billed for the prompt and the output you received. A request that is not streaming answers 504.
- A request body over 64 MiB answers 413
request_too_large. - A request makes at most 8 provider calls, every fallback included;
gateway.retrylowers the ceiling.
Usage and limits collects the limits of every API.
Related
- Tools and structured output, Streaming, Reasoning, Images in requests and Prompt caching cover what the three text APIs share.
- Chat Completions and Responses are the other two text APIs; Choosing an API compares them.
- Coming from OpenRouter lists every difference from OpenRouter in one place.