# API reference

The llm.buzz model API speaks the OpenAI, Anthropic and OpenRouter request and response formats under `https://api.llm.buzz/api/v1`. This page covers what every route shares: the base URL, authentication, the `provider` object, request fields and errors. Each endpoint has its own page, with its fields and examples.

## Base URL and SDKs

Every model route is under `https://api.llm.buzz/api/v1`. Point an SDK at it and change nothing else:

| SDK | Setting |
|---|---|
| OpenAI | `base_url="https://api.llm.buzz/api/v1"` |
| OpenRouter | `server_url="https://api.llm.buzz/api/v1"` (Python), `serverURL: "https://api.llm.buzz/api/v1"` (TypeScript) |
| Anthropic | `base_url="https://api.llm.buzz/api"`, without `/v1`: the SDK adds `/v1/messages` itself |

## Authentication

Send an inference key as a bearer token:

```bash
curl https://api.llm.buzz/api/v1/models \
  -H "Authorization: Bearer $LLM_BUZZ_KEY"
```

The Messages API also takes the key in `x-api-key`, the header the Anthropic SDK sends. A management key calls only the key routes below: create one on the console's [Keys](https://app.llm.buzz/console/keys) page, as you create an inference key, and send it the same way. The samples for those routes read it from `$LLM_BUZZ_MANAGEMENT_KEY`. [Authentication](authentication.md) covers keys, expiry and spending limits.

## Endpoints

| Method and path | What it does |
|---|---|
| `POST /chat/completions` | Chat Completions. Streams with `stream: true`. |
| `POST /responses` | Responses. The same path also takes a WebSocket upgrade on `GET`. |
| `POST /messages` | The Anthropic Messages API. |
| `POST /messages/count_tokens` | An estimate of a Messages request's input tokens, `{"input_tokens": N}`. Costs nothing. |
| `POST /embeddings` | Embeddings, for embeddings models. Never streamed. [Embeddings](embeddings.md) covers it. |
| `POST /images` | OpenRouter's Image API: images from a text prompt, each as base64. Never streamed. [Images](images.md) covers it. |
| `POST /images/generations` | The OpenAI Images API (generations), serving the same image models. Never streamed. |
| `POST /audio/transcriptions` | Speech to text: one audio file in, its text out. Never streamed. [Audio](audio.md) covers both audio routes. |
| `POST /audio/speech` | Text to speech: one text in, the whole audio out as MP3 or PCM. |
| `GET /generation?id=` | What a finished request cost, [below](#cost-of-a-finished-request). |
| `GET /models` | The models your key can call, in OpenRouter's shape. Without a key, the public models. |
| `GET /models/{author}/{slug}` | One model. A model whose name has no `/` is `GET /models/{name}`. |
| `GET /model/{author}/{slug}` | The same model, wrapped as `{"data": ...}`. |
| `GET /models/{author}/{slug}/endpoints` | One model's providers, each with its prices, context length, `tag` and recent uptime, latency and throughput. `GET /models/{name}/endpoints` for a name with no `/`. |
| `GET /embeddings/models` | The embeddings models, as `GET /models?output_modalities=embeddings` lists them. Without a key, the public ones. |
| `GET /images/models` | The image models your key can call, each with the sizes and values its fields take. Key required. |
| `GET /images/models/{author}/{slug}/endpoints` | One image model's providers, each with the values its fields take and its price per token or per image. `GET /images/models/{name}/endpoints` for a name with no `/`. Key required. |
| `GET /key` | The key you send: its kind, its limit, what is left and its spend. |
| `GET /credits` | Your organization's prepaid credit and usage. Management key. |
| `GET, POST /keys`; `GET, PATCH, DELETE /keys/{hash}` | List, create, change and revoke inference keys. Management key. |

Paths are relative to `https://api.llm.buzz/api/v1`. Model routes outside it, such as `/v1/chat/completions` on the bare host, answer 404.

Decisions are served on `POST /systemone` and on `POST https://api.llm.buzz/api/alpha/decisions`, and no listed model answers them today. [Choosing an API](choosing-an-api.md) says which API each kind of model takes, and [Decisions](decisions.md) covers the two routes.

:::note
llm.buzz does not enable batch jobs: with a key, `/batches` and `/files` answer 404, "batches are not enabled on this gateway". Send a large job as single requests; the [batch jobs guide](/docs/guides/batch-jobs) shows how.
:::

The keyless public catalog under `/public/v1` (the site facts, every public model and provider) is what the llm.buzz website is built from, and the [API reference](/docs/api/public-models) lists it too. It needs no key, but it is the website's data, not a stable API: its fields can change without notice. Build on `GET /models` and the endpoints route instead.

## The model list

`GET /models` returns `data`, `total_count` and `links`. Prices are decimal strings in US dollars per token. An audio model's `prompt` price is per second of audio or per character of input, and an image model lists its output price in `image_output` ([Images](images.md) explains image prices). Its query parameters:

| Parameter | Effect |
|---|---|
| `output_modalities` | `text` by default; `all` lists every model, and a comma list such as `text,embeddings` lists models that produce any of them. The values are `text`, `image`, `embeddings`, `transcription` (speech to text) and `speech` (text to speech). |
| `limit`, `offset` | Page the list. |
| `region=eu` | Only models with an EU provider, priced over those providers. It changes the listing, never how a call routes. |

A model whose lead provider's listing names its reasoning efforts carries them as `reasoning_efforts`, in order from `none` to `max`. The key is left out otherwise, including for a model whose efforts llm.buzz knows only from its own rules for the model's family, so a model without it can still reason: [Reasoning](reasoning.md#which-models-reason). A speech-to-text model carries `verbose_json`, and a text-to-speech model `supported_voices`, `default_voice` and `speech_speed`; [Audio](audio.md) explains them.

Without an `Authorization` header the list, a model and its endpoints answer the public catalog, at most 1,000 models, priced at each model's first-ranked public provider.

On the endpoints route, llm.buzz's own provider is one endpoint, `provider_name` `llm.buzz` and `tag` `llm-buzz`, priced at the lowest live direct price of each component. [The llm.buzz provider](models-and-routing.md#the-llmbuzz-provider) explains it.

## The provider object

Send `provider` in the request body (with the OpenAI SDK, in `extra_body`) to steer one request. Chat Completions, Responses, Messages, Embeddings and the image routes read every field; the audio routes read only `zdr` and `data_collection`. Every field accepts `null`.

| Field | Type | Meaning |
|---|---|---|
| `order` | list of strings | Try these providers first, in this order, then the others. |
| `only` | list of strings | Use only these providers. |
| `ignore` | list of strings | Never use these providers. |
| `allow_fallbacks` | boolean | `false` stops moving to other providers: without `order` only the first provider is tried, with `order` only the listed ones. |
| `sort` | string | `price` tries the cheapest provider first, `throughput` and `latency` the fastest, `smart` weighs price and speed. Ignored when `order` is set. |
| `zdr` | boolean | `true` keeps the request on providers that declare 0 days of retention. |
| `data_collection` | string | `deny` keeps the request on providers that declare no training and 0 days of retention. `allow` demands nothing. |

`order`, `only` and `ignore` take at most 64 names. A name matches a provider's display name, its tag, or the tag's first part, ignoring case; an EU provider's tag ends in `/eu`. `llm.buzz` or `llm-buzz` names llm.buzz's own provider, and `llm-buzz/eu` only its capacity in the EU. [Models and routing](models-and-routing.md) explains the order a model's providers are tried in.

```python
extra_body={"provider": {"only": ["scaleway/eu"], "zdr": True}}
```

## Model suffixes

A suffix on the model's name changes one request: `:floor` tries the cheapest provider first, `:nitro` the fastest, and `:exacto` is accepted and ignored. `:online` asks for a web search on Chat Completions and Responses; on Messages it does nothing and is named in `x-sgr-ignored-parameters` as `model:online`.

llm.buzz runs no web search today. A request that asks for one, with `:online`, `web_search_options` or `plugins: [{"id": "web"}]`, is still served, without the search: nothing is sent to a search service, no search is billed, and `x-sgr-ignored-parameters` names it as `web_search->dropped(search_unavailable)`. For an organization whose requests llm.buzz keeps in the EU, such a request answers 400 `web_search_unavailable_in_region` instead.

## Request fields

Each API's page lists every field and what llm.buzz does with it: [Chat Completions](chat-completions.md), [Responses](responses.md) and [Messages](messages.md). Fields the surface does not know are dropped instead of refused, and Chat Completions, Responses and Messages name them in `x-sgr-ignored-parameters` in the response body. A field llm.buzz refuses answers 400 before any provider is called: [Errors](errors.md#the-request). These fields do something on llm.buzz:

| Field | Surfaces | Meaning |
|---|---|---|
| `tools` | Chat Completions, Responses, Messages | Functions the model can call, in each API's own shape. |
| `response_format` | Chat Completions | `json_schema` asks for an answer in a JSON schema; `json_object` for any JSON object. |
| `reasoning` | Chat Completions, Messages | `effort` or `max_tokens`, plus `enabled` and `exclude`. Each provider receives it in its own terms. Where a model's efforts are known, an effort it does not take becomes the nearest one it does, named in `x-sgr-ignored-parameters`; a provider that refuses the setting answers 400 naming the field. `exclude: true` keeps the reasoning text out of the answer while the model still reasons. Responses takes `reasoning.effort`, and Chat Completions also takes OpenRouter's `include_reasoning`: `true` is `reasoning: {}`, `false` is `reasoning: {exclude: true}`. |
| `cache_control` | Chat Completions, Responses | Anthropic's prompt-cache marker, `{"type": "ephemeral"}` with an optional `ttl` of `"5m"` or `"1h"`. llm.buzz passes it on and never adds one. Messages takes Anthropic's own markers. [Prompt caching](prompt-caching.md) says when to send it. |
| `prompt_cache_breakpoint`, `prompt_cache_options`, `prompt_cache_retention` | Chat Completions, Responses | OpenAI's explicit caching controls, for OpenAI's GPT-5.6 and later models. [Prompt caching](prompt-caching.md#openais-explicit-caching) explains them. |
| `session_id` | Chat Completions, Responses, Messages | A name for the conversation, 1 to 256 characters, so its turns stay on the provider that holds its cache. The `x-session-id` header works too. |
| `web_search_options`, `plugins: [{"id": "web"}]` | Chat Completions, Responses | Ask for a web search, as `:online` does. llm.buzz runs none today: see [Model suffixes](#model-suffixes). |
| `provider` | Every model route | The provider object above. |
| `gateway` | Chat Completions, Responses, Messages | Per-request retries and fallbacks, below. |
| `defer_loading` | Chat Completions, Responses, Messages | On a tool, keeps it out of the prompt until a tool search finds it, below. |

On Chat Completions and Responses, send an `Idempotency-Key` header. A retry with the same key and the same body never runs the request twice: it returns the first answer, or 409 `idempotency_replay_unavailable` when that answer is not stored where the retry arrived. Send the same key again only when the first call got no answer, or after an answer carrying `x-gateway-admission-refused: true`; after any other error answer send a new key. A retry without the key, or with a new one, runs the request again and is billed again. [Retries and idempotency](retries-and-idempotency.md) says which key to send after each answer, and [Errors](errors.md#retries) lists the codes.

### Sampling settings a model does not take

Some models refuse sampling settings. The Claude models that only think adaptively (Opus 4.7 onward, Sonnet 5, Haiku 5, Fable 5, Mythos 5) take no `temperature`, `top_p` or `top_k`, and OpenAI's reasoning models take `temperature` and `top_p` only where OpenAI does: some never, others only at reasoning effort `none`. llm.buzz drops such a value instead of failing the request, and names it in `x-sgr-ignored-parameters`, as `temperature->dropped(unsupported_by_provider)`, or `temperature->dropped(set_reasoning_effort_none)` where setting the effort to `none` keeps it.

Two more rules follow the model or the provider:

- **Claude models that always think.** Claude Opus 5.5, Sonnet 5.5 and the Fable models take no `none` effort: `reasoning_effort: none` and `thinking.type: disabled` answer 400 before any provider is called, whichever provider would serve the model. Leave thinking out, or choose an effort: [How an effort is mapped](reasoning.md#how-an-effort-is-mapped). Claude Opus 5.5, Sonnet 5.5 and Fable 5.1 also cannot be forced to call a tool: a `tool_choice` that forces one is served as `auto` and named as `tool_choice->auto`, so the answer may come without a tool call.
- **Moonshot AI's own API**, one provider of the Kimi models such as `moonshotai/kimi-k3`, takes no `temperature`, `top_p`, `top_k`, `frequency_penalty` or `presence_penalty`: llm.buzz drops them there, names them in `x-sgr-ignored-parameters`, and the model answers at its fixed settings. A `tool_choice` that names one function goes to another provider of the model that takes it, or is served as `auto` and named as `tool_choice->auto`. To force a tool call there, send `tool_choice: "required"`. The Kimi models' other providers keep these settings.

## Retries and fallbacks per request

The `gateway` object sets bounds for one request. Every field is optional.

```python
extra_body={"gateway": {
    "retry": {"max_attempts_per_route": 2, "max_total_attempts": 3,
              "backoff": {"type": "exponential", "base_delay_ms": 500, "max_delay_ms": 8000}},
    "routing": {"allow_fallbacks": False},
}}
```

| Field | Meaning |
|---|---|
| `retry.max_attempts_per_route` | 1 to 4 calls to one provider, the first included. A cap, not extra retries: a provider that failed is not called again for an ordinary failure. |
| `retry.max_total_attempts` | 1 to 8 calls in all, every fallback included. 8 when omitted. |
| `retry.backoff` | `{"type": "none"}`, or `{"type": "exponential"}` with `base_delay_ms` (default 500), `max_delay_ms` (default 8000) and `multiplier` (default 2): the wait before calling a throttled provider again. |
| `routing.allow_fallbacks` | `false` keeps the request on the first provider, as `provider.allow_fallbacks: false` does. |

These are ceilings: the request's time limit, your spending limits and a provider's health can stop it sooner. Once output has reached you, the request stays on the provider that sent it.

## Tool search

With many tools, mark most of them `defer_loading: true` and declare a tool search tool. The model then sees only the other tools and a search; the tools a search finds join the request. Where every provider of the model searches natively, the provider does it; otherwise llm.buzz runs the search, up to 3 rounds per request.

| Surface | Declare | Mark |
|---|---|---|
| Messages | `{"type": "tool_search_tool_bm25"}` or `{"type": "tool_search_tool_regex"}` | `defer_loading: true` on the tool |
| Responses | `{"type": "tool_search"}` | `defer_loading: true` on the function tool |
| Chat Completions | `{"type": "openrouter:tool_search"}` | `defer_loading: true` beside `function` |

Usage counts the searches in `server_tool_use_details.tool_search_requests` (`server_tool_use.tool_search_requests` on Messages).

## Responses

- `usage.cost` is the request's cost in US dollars, on every surface, when every part of it is known.
- Chat Completions and Embeddings bodies, and every Chat stream chunk, carry `provider`: the name of the provider that answered, `llm.buzz` for llm.buzz's own provider.
- Cached input shows in `usage.prompt_tokens_details.cached_tokens` on Chat Completions and `usage.input_tokens_details.cached_tokens` on Responses, and cache writes in `cache_write_tokens` beside it, with `cache_write_1h_tokens` for the one-hour part when the provider reports it. Messages keeps Anthropic's fields, `cache_read_input_tokens` and `cache_creation_input_tokens`. [Prompt caching](prompt-caching.md#read-the-cache-in-usage) lists them.
- Every answer carries the headers `x-request-id`, `x-gateway-alias` (the model that served), `x-gateway-canonical-model`, `x-gateway-provider` (the provider that answered), `x-gateway-route-depth` and `x-gateway-route-reason`. Image and audio answers add `X-Generation-Id`.
- Readable reasoning comes back beside the answer: `message.reasoning` (`delta.reasoning` in a stream) on Chat Completions, a `thinking` block on Messages, and a reasoning item's `summary_text` on Responses. `reasoning.exclude: true` turns it off.
- A Chat Completions model that makes images returns them in `message.images[].image_url.url` (`delta.images` in a stream), each a complete base64 image. Send one back as an assistant `image_url` content part to continue the conversation.

## Cost of a finished request

`GET /generation?id=` answers the record and cost of any finished request of your organization. A request's generation ID is `gen-` and the 32 hex digits of its `x-request-id`, which is `request-` and the same digits. Image and audio answers also name it in `X-Generation-Id`, and a text-to-speech answer, which has no JSON body, has its cost only here.

```bash
curl "https://api.llm.buzz/api/v1/generation?id=gen-..." \
  -H "Authorization: Bearer $LLM_BUZZ_KEY"
```

The answer is OpenRouter's `{"data": {...}}`. `total_cost` and `usage` are what you were charged, in US dollars at your rate; `api_type` is `completions` (Chat Completions, Responses and Messages), `embeddings`, `image`, `stt`, `tts` or `decisions`; `provider_name` is the provider that served it. An ID of another organization, a malformed one, and one whose request has not finished all answer 404; a request without `id` answers 400. [Audio](audio.md#cost-and-the-generation-record) lists every key.

## Time limit

A request may run for 600 seconds. A stream still generating then ends with a timeout error chunk and the stream terminator, and is billed for the prompt and the output you received, not for its whole reservation. Any other request past the limit answers 504 `deadline_exceeded`. [Usage and limits](usage-and-limits.md#limits) collects the limits of every API.

## Browser access

A web page can call the model API from the browser with its visitor's own key, as on OpenRouter. The key travels in `Authorization` or `x-api-key`, never in a cookie, so every answer under `/api/` allows any origin and none allows credentials.

- A preflight answers 204 without a key, and a browser keeps its answer for a day. It allows the headers the API reads: `authorization`, `content-type`, `accept`, `x-api-key`, `anthropic-version`, `anthropic-beta`, `x-title`, `http-referer`, `idempotency-key`, `x-session-id`, `x-client-request-id`, `originator`, `openai-organization`, `openai-project`, and any `x-sgr-` or `x-stainless-` header (the OpenAI and Anthropic SDKs send the latter). The browser refuses to send any other header.
- Every answer, errors included, lets the page read the headers `x-request-id`, `x-generation-id`, `retry-after`, `x-ratelimit-*` and `x-gateway-*`, and, in the body, the `x-sgr-ignored-parameters` key. No answer sends that name as a header.
- The keyless catalog under `/public/v1` sends no CORS headers.
- A key used from a page is visible to that page's visitors. Give a browser app a key with a spending limit, or let each visitor bring their own.

## App attribution

Coding agents and apps that llm.buzz recognizes get their usage counted under their own name on [Rankings](/rankings) and their app page. Most are recognized by their `User-Agent`. An app can also name itself with OpenRouter's headers:

```bash
-H "X-Title: Cline" -H "HTTP-Referer: https://cline.bot"
```

A title or a site llm.buzz does not know counts as other usage. Neither header is ever published.

## Errors

Errors on the model routes come in one envelope; `metadata.gateway_code` is the code to branch on. The Messages API keeps Anthropic's error body.

```json
{"error": {"code": 404, "message": "...", "metadata": {"error_type": "not_found", "gateway_code": "model_not_found", "param": "model"}}}
```

| Status | Code | Meaning |
|---|---|---|
| 400 | `invalid_parameter` | A request field fails validation, such as a missing `messages` or `input`, an out-of-range `temperature` or `n` other than 1; `metadata.param` names it, or `body` when the fault spans fields. |
| 400 | `invalid_json` | The body is not JSON. |
| 400 | `unsupported_parameter` | A field llm.buzz refuses; `metadata.param` names it. The pages of Chat Completions, Responses and Messages list the fields each refuses. |
| 400 | `invalid_request` | llm.buzz read the body and refused the request: a provider's own 400, whose sentence follows `provider rejected the request:`, or another refusal the message explains. |
| 400 | `unsupported_capability` | The model or its providers cannot do what the request asks, such as a `service_tier` of `flex`, `priority` or `fast` on Chat Completions and Responses. |
| 400 | `context_length_exceeded` | The prompt fits no provider's context window. |
| 400 | `web_search_unavailable_in_region` | The request asked for a web search, and your organization's requests are kept in the EU. |
| 401 | `invalid_key` | No key, or the key is unknown, expired, revoked or disabled. |
| 402 | `insufficient_credits` | Your balance is spent and the model is not free. |
| 403 | `key_limit_exceeded` | The key's spend in its period has reached its limit. |
| 403 | `management_key_not_allowed` | A management key called a model route. |
| 404 | `model_not_found` | No model has this name, or your key may not call it. |
| 404 | `model_unavailable` | No provider of the model can serve it right now. |
| 404 | `no_allowed_providers` | Your `provider` preferences leave no provider. Nothing was sent. |
| 404 | `model_not_available_under_data_policy` | No provider meets `zdr`, `data_collection` or your organization's data policy. Nothing was sent. |
| 409 | `idempotency_conflict` | An `Idempotency-Key` was reused with a different body. |
| 409 | `idempotency_replay_unavailable` | A request with this `Idempotency-Key` was already accepted, and its answer is not stored where this retry arrived, so it was not run again. Retry with the same key after a short backoff first, when the first call got no answer; a new key runs the request again and bills it again, and after an error answer a new key is the only way. |
| 413 | `request_too_large` | The request body is over the size limit, such as an audio file over 25 MiB. |
| 429 | `unavailable_route` | Providers are throttling. Retry after `Retry-After` seconds. |
| 429 | `free_allowance_exceeded` | A free offer's allowance is used: your organization's tokens, or your key's requests per minute. Choose a paid model, or retry after `Retry-After` seconds for the per-minute limit. |
| 502 | `all_routes_failed` | Every provider tried failed. Retry with backoff. |
| 503 | `unavailable_route` | No provider could answer just now. Retry with backoff. |
| 504 | `deadline_exceeded` | The request passed its 600-second limit. |

[Errors](errors.md) lists every code with what to do.
