Get API key
APIs

Responses

Responses is OpenAI's API of items in and items out, with the conversation's state kept by llm.buzz, so a follow-up sends only its own input. llm.buzz serves it at POST https://api.llm.buzz/api/v1/responses for every text model, and the OpenAI SDK and Codex call it with the base URL changed.

On this page

Send a request

input is a string or a list of items, and instructions is the system instruction for this request. The OpenAI SDKs give the reply text as output_text.

from openai import OpenAI
import os

client = OpenAI(
    base_url="https://api.llm.buzz/api/v1",
    api_key=os.environ["LLM_BUZZ_KEY"],
)
response = client.responses.create(
    model="openai/gpt-5.6-luna",
    instructions="You answer in one short sentence.",
    input="Why do HTTP clients retry with backoff?",
)
print(response.output_text)

The answer is OpenAI's response object with the additions below. The model name, and the models you can call, come from GET https://api.llm.buzz/api/v1/models (Choosing an API).

Continue a conversation

llm.buzz keeps each finished turn. Send previous_response_id with the id of the last answer and only this turn's input, and llm.buzz adds the turns before it.

first = client.responses.create(
    model="openai/gpt-5.6-luna",
    instructions="You answer in one short sentence.",
    input="Which port does HTTPS use?",
)
second = client.responses.create(
    model="openai/gpt-5.6-luna",
    instructions="You answer in one short sentence.",
    previous_response_id=first.id,
    input="And plain HTTP?",
)
print(second.output_text)
  • A turn is kept for up to 24 hours. It is not a prompt log: the console does not show it, and it is kept even when your organization turned prompt logging off or the request demanded zero retention (Models and routing). Send store: false to keep a turn out.
  • A request that continues a turn llm.buzz no longer has answers 400 previous_response_not_found with param: previous_response_id, and its message asks you to resend the whole conversation in input. A turn is gone after 24 hours, when it was sent with store: false, and sooner if llm.buzz's store of turns restarts or fills up. The Codex client resends by itself.
  • When llm.buzz cannot reach its store of turns, a continuation answers 503 continuation_store_unavailable. Wait Retry-After seconds and retry. A turn that finishes while the store is unreachable is answered but not kept, so it cannot be continued later.
  • Each turn follows only its own instructions. Send them again on every turn that should follow them.
  • A kept turn belongs to the organization, key identity and model that made it. A continuation that names another model answers 400 previous_response_not_found, so continue with the same model.
  • A continuation can stay on the provider that served the turn it continues. While that provider can still be used, provider.order and the routing preset do not move it, and only and ignore do not narrow it, so send provider preferences with the first turn. A continuation also keeps the caching session its first turn resolved, unless it sends its own session_id, x-session-id or prompt_cache_key (Prompt caching).

Input items

input is a string, which counts as one user message, or a list of items:

  • Messages, {"role": "user", "content": ...}, with the roles user, assistant, system and developer. A developer or system item is accepted for every model. A leading one is the model's instruction. A later one stays in place when the provider takes one there, and otherwise moves into the user text at the same place, named in the body's x-sgr-ignored-parameters, for example messages.system->folded(system_messages_leading_only).
  • function_call and function_call_output items carry a tool loop. Tool call IDs are opaque strings of 1 to 65,536 characters: send each one back complete and unchanged, in the call and in its output. A function_call_output's output is a string or a list of text and image parts, and any other part kind is a 400 that names it. Tools and structured output covers the loop.
  • reasoning items from an earlier answer, sent back as they came. Ask for include: ["reasoning.encrypted_content"] and a provider that supports it returns the model's reasoning encrypted in its reasoning items. Send those items back with the next request to keep the model's thinking across tool calls in a conversation sent with store: false. An encrypted item is tied to the provider account that produced it. When a provider refuses one you sent back, llm.buzz retries without the encrypted reasoning items, keeps the rest of the conversation, and sets the response header x-gateway-replay-repair: encrypted_reasoning_stripped.

Request fields

A field llm.buzz deliberately refuses is a 400 before any provider is called, and a value that fails validation is a 400 invalid_parameter with metadata.param naming the field. A field llm.buzz drops, such as messages or stop, which Responses does not know, is named in the body's x-sgr-ignored-parameters.

FieldWhat llm.buzz does
modelThe model's name, author/slug. Four suffixes change one request: :floor tries the cheapest provider first, :nitro the fastest, :exacto is accepted and ignored, and :online is accepted and searches nothing (see the web plugin below). Models and routing.
input, instructionsinput is required: a request without it is 400 invalid_parameter. Input items. instructions is the system instruction of this request alone; an empty or whitespace-only value counts as absent.
previous_response_id, storeprevious_response_id continues a kept turn: Continue a conversation. store: false keeps this turn out, so nothing can continue it.
max_output_tokensThe most output tokens, reasoning included. Without one, llm.buzz does not choose a smaller limit for you. A provider that needs a larger minimum than your limit is skipped, and when none is left the request is refused.
temperature, top_pSent to the provider; an out-of-range temperature is 400 invalid_parameter. A model that takes none, or takes them only at reasoning effort none, has the value dropped and named: Sampling settings a model does not take.
top_kSent to providers that take it. Dropped and named as top_k->dropped(unsupported_by_provider) when none does.
seed, logit_biasDropped and named. Chat Completions refuses them; Responses does not.
stop, n, frequency_penalty, presence_penalty, response_format, stream_options, audio, modalities, functions, function_call, predictionNot fields of the Responses API, so dropped and named. Chat Completions refuses audio, modalities, functions, function_call and prediction; Responses does not.
streamtrue streams the answer as server-sent events: Stream.
tools, tool_choiceSent, in OpenAI'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.
parallel_tool_callsHonored on every provider. Where a provider has no such control, true is its default behavior and is named parallel_tool_calls->dropped(provider_default), and false is enforced by llm.buzz, which returns only the first tool call of a turn and names it parallel_tool_calls->emulated(serialized_by_gateway).
texttext.format with type: "json_schema" asks for JSON that matches your schema: Tools and structured output. text.format.type accepts only text and json_schema; any other, json_object included, is 400 invalid_parameter naming text.format.type. text.verbosity is a hint, sent to providers that take it and dropped and named elsewhere.
includereasoning.encrypted_content returns encrypted reasoning (Input items). message.output_text.logprobs, with top_logprobs, asks for token probabilities: llm.buzz sends both to providers that serve the native Responses API, and the provider returns, ignores or refuses them. Any other selector is 400 invalid_parameter naming include, the six that would echo server-tool output or an input image among them: code_interpreter_call.outputs, computer_call_output.output.image_url, file_search_call.results, message.input_image.image_url, web_search_call.action.sources and web_search_call.results.
reasoningeffort, summary, generate_summary and context are accepted. If you send both summary and generate_summary they must match, or the request is a 400 invalid_parameter naming reasoning. effort is translated into each provider's own terms (Reasoning). Without a summary, llm.buzz asks OpenAI for an automatic one unless the effort is none. reasoning.mode is 400 invalid_parameter.
truncationOnly "disabled". Any other value is 400 invalid_parameter.
client_metadataSent to providers that serve the native Responses API and dropped and named elsewhere.
metadataEchoed on the response object: The answer.
cache_controlAnthropic's prompt-cache marker, {"type": "ephemeral"} with an optional ttl of "5m" or "1h", at the top level only. A marker on a tool is a 400. Passed on or translated, never added by llm.buzz. Prompt caching.
prompt_cache_breakpointOpenAI's explicit cache mark, {"mode": "explicit"}, on an input_text part of a system, developer or user message item or of a function_call_output. Sent to OpenAI as given. For Claude it becomes a five-minute cache_control marker, named prompt_cache_breakpoint->translated(cache_control). On any other part, or on an assistant message, it is a 400 that names where it was. Prompt caching.
prompt_cache_options, prompt_cache_retentionOpenAI's cache mode and retention. Sent to OpenAI only; no other provider receives them, and where no provider of the model takes them, x-sgr-ignored-parameters names them as not forwarded. prompt_cache_retention: "24h" on a request that demands zero retention is a 400 naming it. Prompt caching.
prompt_cache_keyNames the conversation for caching; promptCacheKey works too. It names the session when session_id and x-session-id are absent.
session_idA 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 400 invalid_parameter naming session_id. The x-session-id header names it too.
providerSteers 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. Official SDKs send it through extra_body. The provider object.
gatewayllm.buzz's own per-request bounds, retry and routing.allow_fallbacks. Never sent to a provider. A warm-up frame on the WebSocket refuses it. Retries and fallbacks per request.
service_tierauto and default are served at the standard tier and not sent to the provider. flex, priority and fast are 400 unsupported_capability naming service_tier, before any provider is called: llm.buzz sells no priority tier, and no model has a price for one today.
The web pluginAccepted, and nothing is searched: llm.buzz runs no web search. The request is served without one, nothing is billed for one, and x-sgr-ignored-parameters names web_search->dropped(search_unavailable). For an organization whose requests llm.buzz keeps in the EU, the request is 400 web_search_unavailable_in_region.
other plugins entriesDropped and named by id, as plugins.response-healing.
models, route, transforms, usage, trace, debugDropped 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.
background, context_management, conversation, max_tool_calls, moderation, promptRefused: 400 unsupported_parameter naming the field.
defer_loading on a toolKeeps the tool out of the prompt until a tool search finds it. Tool search.

Headers

You send:

HeaderUse
AuthorizationBearer and your inference key. Authentication.
Idempotency-KeyMakes a retry safe: Retries.
x-session-idNames the conversation, as session_id does.
X-Title, HTTP-RefererName your app. App attribution.
x-client-request-idYour own correlation ID. llm.buzz echoes it on the answer. It also names the conversation for caching when you send no session_id, x-session-id, prompt_cache_key or previous_response_id; a value that changes on every request gives each request its own cache key, so send one stable value per conversation, or none.

Every served answer carries:

HeaderValue
x-request-idThe request's ID. Its generation ID is gen- and the same 32 hex digits: Cost of a finished request.
x-gateway-aliasThe model name that served.
x-gateway-canonical-modelThe exact model that produced the answer.
x-gateway-providerThe display name of the provider that answered, llm.buzz for llm.buzz's own provider. The response object has no provider key, so read it here.
x-gateway-route-depth, x-gateway-route-reasonThe 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. An HTTP answer also carries x-gateway-replay-repair: encrypted_reasoning_stripped when llm.buzz retried without the encrypted reasoning items you sent back (Input items).

The answer

A non-streamed answer is OpenAI's response object. llm.buzz adds or fixes these parts:

  • output is a list of items: message items with output_text parts, function_call items, and reasoning items. A reasoning item's summary_text holds the model's readable reasoning where the provider gives one, and its encrypted_content holds the encrypted reasoning when you asked for it. Reasoning.
  • status is completed for a finished answer and incomplete when the output ceiling cut it.
  • instructions, metadata and tools echo the request: instructions is null when you sent none, metadata is {}, and tools keeps your order, with each function tool as served.
  • frequency_penalty and presence_penalty are present, as in OpenAI's response object.
  • usage is described below.
  • x-sgr-ignored-parameters is a key in the body, not a header. It lists every field llm.buzz dropped and every value it changed, with the reason, such as temperature->dropped(unsupported_by_provider). Check it when a setting seems to have no effect.

Stream

Set stream: true and the answer arrives as server-sent events, OpenAI's Responses stream events: response.created, response.in_progress, then the output events, such as response.output_text.delta, and exactly one terminal event. The response object in those events carries the echo of instructions, metadata and tools, and x-sgr-ignored-parameters, as a non-streamed answer's does. Closing a stream sent without an Idempotency-Key stops the request, and the prompt and the output produced up to then are billed. Streaming covers the event shapes of all three text APIs.

WebSocket

GET https://api.llm.buzz/api/v1/responses with a WebSocket upgrade is the transport Codex uses. Authenticate with the same Authorization header as an HTTP request.

  • You send response.create frames. llm.buzz answers each with the response's stream events, one event as JSON in each text frame.
  • A failure of the request itself arrives as an OpenAI error frame, {"type": "error", ...}, and not as an HTTP status: Errors.
  • A frame with generate: false is a warm-up: llm.buzz answers it without calling a provider.
  • llm.buzz checks the key before it accepts the upgrade, and a management key is refused with 403 at that point. A GET that is not a well-formed upgrade answers 426 upgrade_required; the Codex client falls back to HTTP on it.
  • The socket closes at the 600-second deadline (Limits). The requests a socket carries do not appear in prompt logs, and an Idempotency-Key on the upgrade request does not apply to the frames.

Usage and cost

FieldValue
input_tokens, output_tokensThe counts the provider reported for the attempt that answered.
input_tokens_details.cached_tokensInput tokens read from the provider's cache. Always present, 0 when the provider reported none.
input_tokens_details.cache_write_tokensInput tokens written to it. Always present, 0 when the provider reported none.
input_tokens_details.cache_write_1h_tokensThe one-hour part of the writes, only when the provider reports the split.
costThe request's total cost in US dollars at your rate.

Cache reads and writes are separate parts of input_tokens. 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. A replay with the same Idempotency-Key returns the original cost. Usage and limits covers the rest, and Prompt caching the cache fields.

Retries

Send an Idempotency-Key header on an HTTP request and a retry with the same key and the same body never runs the request twice: it returns the first answer with its original cost, or 409 idempotency_replay_unavailable when that answer is not stored where the retry arrived. That 409 means the first request ran or is still running, so retry with the same key after a short backoff. The exception is a first call that ended in an error answer: its key answers 409 for good, so send a new key. What to send next tells the cases apart.

  • The same key with another body is 409 idempotency_conflict. A changed suffix or provider hint counts as another body.
  • A retry without the key, or with a new one, runs the request again and bills it again.
  • 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.
  • The OpenAI SDK also retries failed requests on its own, and each retry is a separate request. Set max_retries=0 when your application must bound the work of one operation.

Retries and idempotency explains the rest.

Errors

Over HTTP, errors come in OpenRouter's envelope. metadata.gateway_code is the code to branch on, and metadata.param names the field at fault.

{"error": {"code": 400, "message": "...", "metadata": {"error_type": "invalid_request", "gateway_code": "unsupported_parameter", "param": "background"}}}

On the WebSocket, a failure of the request arrives as OpenAI's error frame, with the status in the frame, for example when the key's spending limit is reached:

{"type": "error", "status": 403, "error": {"message": "Key limit exceeded", "type": "permission_error", "param": null, "code": "key_limit_exceeded"}}
StatusCodeWhen
400invalid_parameterA field fails validation and metadata.param names it, or body when the fault spans fields: a missing input, an out-of-range temperature, a session_id over 256 characters, a truncation other than "disabled", reasoning.mode, an include selector other than reasoning.encrypted_content and message.output_text.logprobs, or a text.format.type other than text or json_schema. Fix the field.
400invalid_jsonThe body is not JSON.
400unsupported_parameterYou sent a refused field: background, context_management, conversation, max_tool_calls, moderation or prompt. Remove it.
400previous_response_not_foundprevious_response_id names a turn llm.buzz does not have: expired, sent with store: false, lost, or made by another model. Resend the whole conversation in input.
400unsupported_capabilityservice_tier is flex, priority or fast, or no provider of the model offers what the request asks. metadata.param names it.
400invalid_requestA refusal after llm.buzz read the body: the provider's own 400, whose sentence follows provider rejected the request:, or another admission refusal. The message says which.
400context_length_exceededThe prompt fits no provider's context window.
400web_search_unavailable_in_regionThe request asked for a web search and your organization's requests stay in the EU.
409idempotency_conflictThe Idempotency-Key was used with another body.
409idempotency_replay_unavailableThe request ran or is running, and its answer is not stored where this retry arrived. Retry with the same key after a short backoff. If the first call ended in an error answer, the key answers 409 for good: send a new key.
413request_too_largeThe body is over 64 MiB.
426upgrade_requiredA GET to /responses that is not a WebSocket upgrade. Open a WebSocket, or POST the request.
503continuation_store_unavailablellm.buzz cannot reach its store of turns. Retry after Retry-After seconds.

Errors lists every code, including the key, balance, model and provider errors this API shares with the others.

Limits

  • A model's context window is context_length on its entry in GET https://api.llm.buzz/api/v1/models, and top_provider.max_completion_tokens is 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, and a WebSocket closes. A request that is not streaming answers 504 deadline_exceeded.
  • A request body over 64 MiB answers 413 request_too_large.
  • A request makes at most 8 provider calls, every fallback included; gateway.retry lowers the ceiling.
  • A kept turn can be continued for up to 24 hours.

Usage and limits collects the limits of every API.

Docs