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.
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)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.llm.buzz/api/v1",
apiKey: process.env.LLM_BUZZ_KEY,
});
const response = await client.responses.create({
model: "openai/gpt-5.6-luna",
instructions: "You answer in one short sentence.",
input: "Why do HTTP clients retry with backoff?",
});
console.log(response.output_text);curl https://api.llm.buzz/api/v1/responses \
-H "Authorization: Bearer $LLM_BUZZ_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.6-luna",
"instructions": "You answer in one short sentence.",
"input": "Why do HTTP clients retry with backoff?"
}'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)const first = await client.responses.create({
model: "openai/gpt-5.6-luna",
instructions: "You answer in one short sentence.",
input: "Which port does HTTPS use?",
});
const second = await 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?",
});
console.log(second.output_text);# RESPONSE_ID is the "id" of the first answer
curl https://api.llm.buzz/api/v1/responses \
-H "Authorization: Bearer $LLM_BUZZ_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.6-luna",
"instructions": "You answer in one short sentence.",
"previous_response_id": "'"$RESPONSE_ID"'",
"input": "And plain HTTP?"
}'- 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: falseto keep a turn out. - A request that continues a turn llm.buzz no longer has answers 400
previous_response_not_foundwithparam: previous_response_id, and its message asks you to resend the whole conversation ininput. A turn is gone after 24 hours, when it was sent withstore: 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. WaitRetry-Afterseconds 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.orderand the routing preset do not move it, andonlyandignoredo not narrow it, so sendproviderpreferences with the first turn. A continuation also keeps the caching session its first turn resolved, unless it sends its ownsession_id,x-session-idorprompt_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 rolesuser,assistant,systemanddeveloper. Adeveloperorsystemitem 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'sx-sgr-ignored-parameters, for examplemessages.system->folded(system_messages_leading_only). function_callandfunction_call_outputitems 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. Afunction_call_output'soutputis 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.reasoningitems from an earlier answer, sent back as they came. Ask forinclude: ["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 withstore: 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 headerx-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.
| Field | What llm.buzz does |
|---|---|
model | The 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, instructions | input 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, store | previous_response_id continues a kept turn: Continue a conversation. store: false keeps this turn out, so nothing can continue it. |
max_output_tokens | The 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_p | Sent 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_k | Sent to providers that take it. Dropped and named as top_k->dropped(unsupported_by_provider) when none does. |
seed, logit_bias | Dropped and named. Chat Completions refuses them; Responses does not. |
stop, n, frequency_penalty, presence_penalty, response_format, stream_options, audio, modalities, functions, function_call, prediction | Not fields of the Responses API, so dropped and named. Chat Completions refuses audio, modalities, functions, function_call and prediction; Responses does not. |
stream | true streams the answer as server-sent events: Stream. |
tools, tool_choice | Sent, 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_calls | Honored 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). |
text | text.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. |
include | reasoning.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. |
reasoning | effort, 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. |
truncation | Only "disabled". Any other value is 400 invalid_parameter. |
client_metadata | Sent to providers that serve the native Responses API and dropped and named elsewhere. |
metadata | Echoed on the response object: The answer. |
cache_control | Anthropic'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_breakpoint | OpenAI'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_retention | OpenAI'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_key | Names the conversation for caching; promptCacheKey works too. It names the session when session_id and x-session-id are absent. |
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 400 invalid_parameter 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. Official SDKs send it through extra_body. The provider object. |
gateway | llm.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_tier | auto 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 plugin | Accepted, 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 entries | Dropped and named by id, as plugins.response-healing. |
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. |
background, context_management, conversation, max_tool_calls, moderation, prompt | Refused: 400 unsupported_parameter naming the field. |
defer_loading on a tool | Keeps the tool out of the prompt until a tool search finds it. Tool search. |
Headers
You send:
| Header | Use |
|---|---|
Authorization | Bearer and your inference key. Authentication. |
Idempotency-Key | Makes a retry safe: Retries. |
x-session-id | Names the conversation, as session_id does. |
X-Title, HTTP-Referer | Name your app. App attribution. |
x-client-request-id | Your 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:
| 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 response object 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. 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:
outputis a list of items:messageitems withoutput_textparts,function_callitems, andreasoningitems. A reasoning item'ssummary_textholds the model's readable reasoning where the provider gives one, and itsencrypted_contentholds the encrypted reasoning when you asked for it. Reasoning.statusiscompletedfor a finished answer andincompletewhen the output ceiling cut it.instructions,metadataandtoolsecho the request:instructionsisnullwhen you sent none,metadatais{}, andtoolskeeps your order, with each function tool as served.frequency_penaltyandpresence_penaltyare present, as in OpenAI's response object.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). 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.createframes. 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: falseis 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
GETthat is not a well-formed upgrade answers 426upgrade_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-Keyon the upgrade request does not apply to the frames.
Usage and cost
| Field | Value |
|---|---|
input_tokens, output_tokens | The counts the provider reported for the attempt that answered. |
input_tokens_details.cached_tokens | Input tokens read from the provider's cache. Always present, 0 when the provider reported none. |
input_tokens_details.cache_write_tokens | Input tokens written to it. Always present, 0 when the provider reported none. |
input_tokens_details.cache_write_1h_tokens | The one-hour part of the writes, only when the provider reports the split. |
cost | The 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 orproviderhint 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=0when 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"}}| Status | Code | When |
|---|---|---|
| 400 | invalid_parameter | A 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. |
| 400 | invalid_json | The body is not JSON. |
| 400 | unsupported_parameter | You sent a refused field: background, context_management, conversation, max_tool_calls, moderation or prompt. Remove it. |
| 400 | previous_response_not_found | previous_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. |
| 400 | unsupported_capability | service_tier is flex, priority or fast, or no provider of the model offers what the request asks. metadata.param names it. |
| 400 | invalid_request | A 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. |
| 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 stay in the EU. |
| 409 | idempotency_conflict | The Idempotency-Key was used with another body. |
| 409 | idempotency_replay_unavailable | The 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. |
| 413 | request_too_large | The body is over 64 MiB. |
| 426 | upgrade_required | A GET to /responses that is not a WebSocket upgrade. Open a WebSocket, or POST the request. |
| 503 | continuation_store_unavailable | llm.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_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, 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.retrylowers the ceiling. - A kept turn can be continued for up to 24 hours.
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 Messages are the other two text APIs; Choosing an API compares them.
- Coming from OpenRouter lists every difference from OpenRouter in one place.