Responses format
Creates a model response: you pass text or a list of input items in input, and it returns a response object with an id, with the output in the output array, where each item is a message, a function call or reasoning content; streaming is supported. It is compatible with the OpenAI Responses API: the OpenAI SDK's client.responses and clients that use the Responses protocol (such as the Codex CLI) work once you set the Base URL to https://api.lyrouter.com/v1, the same as for Chat Completions. The gateway forwards requests in the Responses protocol without protocol conversion, so the selected model and the channel serving it must both support Responses.
/v1/responsesAuthentication
Send your API key in a request header:
Authorization: Bearer <API Key>
Bearer is case-sensitive. You can use x-api-key: <API Key> instead; when both are present, only Authorization is read. Create API keys on the workspace's API Keys page. Your API key is used only to authenticate with LYRouter and is never forwarded to the upstream provider. See Base URLs and authentication.
Headers
| Header | Required | Description |
|---|---|---|
Content-Type |
No | application/json is recommended. Without it, the gateway parses the request body as JSON; any other value (such as application/x-www-form-urlencoded) returns 400 |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
model |
string | No | Model ID; you can also write @vmodel/<name> (virtual model) or lyrouter/auto (Auto routing). When it is omitted, "" or null, the workspace's Default Model is used; without a Default Model, the request returns 400 |
input |
string / array | Yes | A string is equivalent to one user message; an array holds message items and other input items in order, see "Items in the input array" below |
instructions |
string | No | System-level instructions. With a virtual model, the virtual model's system prompt is appended after these instructions |
stream |
boolean | No | When true, the response is returned as an SSE event stream; see "Streaming" below |
tools |
array | No | Function tools are written as {"type": "function", "name", "description", "parameters", "strict"}, with the fields flat rather than wrapped in function as in Chat. Built-in tools (such as web_search) are forwarded as is; whether they work depends on the upstream |
tool_choice |
string / object | No | auto, none, required, or {"type": "function", "name": "..."} |
parallel_tool_calls |
boolean | No | Whether several function calls may be returned at once |
max_output_tokens |
integer | No | Output limit, including reasoning tokens. Use this field; Chat's max_tokens is forwarded as is, and whether it takes effect is up to the upstream |
reasoning |
object | No | For example {"effort": "low", "summary": "auto"}. Passed through to the upstream; whether it takes effect depends on the model |
text.format |
object | No | Output format: {"type": "text"}, {"type": "json_object"}, or {"type": "json_schema", "name": "...", "schema": {...}, "strict": true}. The effect depends on the upstream: for example, some upstreams still wrap the JSON in a Markdown json code block with json_object, and do not follow the schema with json_schema (the text.format echoed in the response names the field schema_). Verify with the selected model before relying on structured output, and strip code block markers before parsing |
temperature, top_p |
number | No | Passed through to the upstream; reasoning models usually do not support them |
store |
boolean | No | Whether the upstream stores this response. The gateway forwards it as is; when it is omitted, the upstream default applies (true for OpenAI) |
previous_response_id |
string | No | Continues from the previous response, with the upstream assembling the context; see "Multi-turn conversations and function calls" below |
include |
array | No | Extra content to return, for example ["reasoning.encrypted_content"]. Passed through |
metadata |
object | No | Passed through |
Items in the input array
| Item | Format | Purpose |
|---|---|---|
| Message | {"role": "user", "content": "..."}. role can be user, assistant, system or developer; content can be a string or an array of content parts such as input_text and input_image |
Passes conversation content. Image input requires model support; see Multimodal capabilities |
| Function call result | {"type": "function_call_output", "call_id": "...", "output": "..."} |
Returns a tool's result to the model |
| Output items from the previous turn | The message, function_call and reasoning items from the previous response's output, put back unchanged |
Lets the client carry the history itself when it does not use previous_response_id |
Request examples
curl https://api.lyrouter.com/v1/responses \
-H "Authorization: Bearer $LYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "MODEL_ID",
"instructions": "You are a concise assistant.",
"input": "Explain what an API gateway is in one sentence.",
"max_output_tokens": 1024
}'
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LYROUTER_API_KEY"],
base_url="https://api.lyrouter.com/v1",
)
response = client.responses.create(
model="MODEL_ID",
instructions="You are a concise assistant.",
input="Explain what an API gateway is in one sentence.",
max_output_tokens=1024,
)
print(response.output_text)
print(response.usage)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LYROUTER_API_KEY,
baseURL: "https://api.lyrouter.com/v1",
});
const response = await client.responses.create({
model: "MODEL_ID",
instructions: "You are a concise assistant.",
input: "Explain what an API gateway is in one sentence.",
max_output_tokens: 1024,
});
console.log(response.output_text);
console.log(response.usage);
Replace MODEL_ID with a model ID that is enabled in your workspace and supports Responses. output_text is a convenience property of the SDK that joins all output_text content; the raw JSON has no such field, so iterate over output when you parse the HTTP response directly. The Python example requires openai Python SDK 1.66 or later; older versions have no client.responses and raise AttributeError: 'OpenAI' object has no attribute 'responses'.
Response
A non-streaming request returns a response object; an error returns an error object. The 200 example below is a typical response with a reasoning summary; the 400 means the selected model does not support Responses; the 403 means the workspace has not enabled this model.
{
"id": "resp_68ee1b9a52c48190a71c3e7d6a0b2f5e",
"object": "response",
"created_at": 1760427600,
"status": "completed",
"error": null,
"incomplete_details": null,
"model": "gpt-5.5",
"instructions": "You are a concise assistant.",
"output": [
{
"id": "rs_68ee1b9ab1d08190b3a2c5e7f9013d4a",
"type": "reasoning",
"summary": [
{ "type": "summary_text", "text": "The user wants a one-sentence definition, so give the conclusion directly." }
]
},
{
"id": "msg_68ee1b9b0e7c8190b9f2d4a6c8e01a37",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "An API gateway is the single entry point between clients and backend services, handling authentication, routing, rate limiting and monitoring.",
"annotations": []
}
]
}
],
"max_output_tokens": 1024,
"previous_response_id": null,
"reasoning": { "effort": "low", "summary": "auto" },
"store": true,
"text": { "format": { "type": "text" } },
"tool_choice": "auto",
"tools": [],
"usage": {
"input_tokens": 1830,
"input_tokens_details": { "cached_tokens": 1536 },
"output_tokens": 196,
"output_tokens_details": { "reasoning_tokens": 128 },
"total_tokens": 2026
},
"metadata": {}
}
{
"error": {
"message": "This model does not support the responses API",
"type": "invalid_request_error",
"code": "unsupported_model_api"
}
}
{
"error": {
"message": "Model is not available in the model plaza",
"type": "access_denied",
"code": "model_not_available"
}
}
| Field | Description |
|---|---|
id |
Response ID generated by the upstream, commonly starting with resp_ (some upstreams return IDs starting with resp-); it is not LYRouter's Request ID. Used for previous_response_id |
status |
completed; incomplete when the output reaches max_output_tokens, with the reason in incomplete_details.reason; it can also be failed or cancelled, and queued or in_progress for background runs |
model |
The actual model name returned by the upstream, which can differ from the model ID in the request. Corresponds to "Model (actual)" in the logs |
output |
Array of output items; handle each one by type: a message item's content holds output_text; function_call is a function call; reasoning is a reasoning summary or encrypted reasoning content. Do not parse it the Chat way through choices[0].message |
usage |
Token usage for this call. The gateway returns the upstream's values unchanged; see the table below |
usage and the four token types in logs
How usage maps to the four types under "Token usage" in the request logs:
usage field |
Meaning | Item in the logs |
|---|---|---|
input_tokens |
All input, including the part that hit the cache | "Input (cache miss)" + "Input (cache hit)" |
input_tokens_details.cached_tokens |
Input that hit the upstream prompt cache | "Input (cache hit)" |
output_tokens |
All output, including reasoning tokens | "Output" |
output_tokens_details.reasoning_tokens |
The part of the output used for reasoning | Included in "Output" |
In the example above, "Input (cache miss)" is 1830 − 1536 = 294, "Input (cache hit)" is 1536 and "Output" is 196; Responses upstreams do not produce "Explicit cache write". For billing, see Estimating costs and cache pricing.
Streaming
With "stream": true, the response is text/event-stream. Each event consists of an event: line and a data: line; type in data matches the event name, and sequence_number increases. In the actual stream each event is also preceded by an id: line, and there may be no space after the colon in event: and data: (as in data:{...}); both forms are valid under the SSE specification, so handle both if you write your own parser. The fragment below omits the id: lines and some fields of the response object:
event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_68ee1b9a52c48190a71c3e7d6a0b2f5e","object":"response","status":"queued","output":[],"usage":null}}
event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_68ee1b9b0e7c8190b9f2d4a6c8e01a37","type":"message","status":"in_progress","role":"assistant","content":[]}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_68ee1b9b0e7c8190b9f2d4a6c8e01a37","output_index":0,"content_index":0,"delta":"An API gateway is"}
event: response.output_text.done
data: {"type":"response.output_text.done","sequence_number":9,"item_id":"msg_68ee1b9b0e7c8190b9f2d4a6c8e01a37","output_index":0,"content_index":0,"text":"An API gateway is the single entry point between clients and backend services, handling authentication, routing, rate limiting and monitoring."}
event: response.completed
data: {"type":"response.completed","sequence_number":12,"response":{"id":"resp_68ee1b9a52c48190a71c3e7d6a0b2f5e","object":"response","status":"completed","usage":{"input_tokens":1830,"input_tokens_details":{"cached_tokens":1536},"output_tokens":196,"output_tokens_details":{"reasoning_tokens":128},"total_tokens":2026}}}
| Event | Meaning |
|---|---|
response.created, response.in_progress |
The response has been created / is being generated. The status in response.created may be queued |
response.output_item.added / .done |
An output item (message, function call, reasoning) starts / ends |
response.content_part.added / .done |
A content part within a message starts / ends |
response.output_text.delta / .done |
Text delta / full text |
response.reasoning_text.delta / .done |
Reasoning text delta / full reasoning text, returned by reasoning models |
response.function_call_arguments.delta / .done |
Function call argument delta / full arguments |
response.completed |
Normal end; response.usage is the usage for this call |
response.incomplete, response.failed |
The output was truncated or generation failed; these also carry the full response object |
- A Responses stream has no
data: [DONE]; it ends withresponse.completed(orresponse.incomplete,response.failed). The gateway does not addstream_optionsto Responses requests, and usage appears only in the final event. - If the upstream breaks off after output has started, the gateway appends a
response.failedevent at the end of the stream and ends it:response.statusisfailed,response.error.codeisserver_errorandresponse.idis the ID of this stream's response, with noresponse.completed. Treat a stream that has no final event as failed; see Timeouts and interrupted streams.
curl -N https://api.lyrouter.com/v1/responses \
-H "Authorization: Bearer $LYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"MODEL_ID","input":"Write a two-line poem","stream":true}'
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LYROUTER_API_KEY"],
base_url="https://api.lyrouter.com/v1",
)
stream = client.responses.create(
model="MODEL_ID",
input="Write a two-line poem",
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
print("\n", event.response.usage)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LYROUTER_API_KEY,
baseURL: "https://api.lyrouter.com/v1",
});
const stream = await client.responses.create({
model: "MODEL_ID",
input: "Write a two-line poem",
stream: true,
});
for await (const event of stream) {
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta);
} else if (event.type === "response.completed") {
console.log("\n", event.response.usage);
}
}
For a comparison of streaming formats across protocols, see Streaming.
Multi-turn conversations and function calls
There are two ways to hold a multi-turn conversation:
previous_response_id: put the previous response'sidin the next request and send only the new input; the upstream stores and assembles the context. This requires upstream support, and the previous turn must not have setstore: false.- Carry the history yourself: include the full history in
inputevery time, appending the items from the previousoutputunchanged, followed by the new input. This does not depend on state stored by the upstream, so it is more reliable when the model has several channels or credentials. If you do not need the upstream to store anything, you can setstore: false; in that case, to send reasoning items back, also set"include": ["reasoning.encrypted_content"].
The gateway uses previous_response_id to group the requests of one continuation chain into a session, which you can see in the "Session" column of the request logs. The gateway does not pin a continuation request to the upstream that created the previous response: when the model has several channels or credentials, a continuation request can land on an upstream that did not store that response, and the upstream returns an error saying the response cannot be found.
The full function call flow is as follows. The first request declares the tool:
{
"model": "MODEL_ID",
"input": "What's the weather in Shanghai right now?",
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"],
"additionalProperties": false
},
"strict": true
}
]
}
When the model decides to call the tool, a function_call item appears in output, with arguments as a JSON string:
{
"id": "fc_68ee1c20a3f88190c2d4e6f8a0b1c3d5",
"type": "function_call",
"status": "completed",
"call_id": "call_k3Jw9Xq2mZ7tVb1R",
"name": "get_weather",
"arguments": "{\"city\":\"Shanghai\"}"
}
Run the tool on the client, then return the result as function_call_output; call_id must match the call. The example below carries the history itself and includes the original tools again:
{
"model": "MODEL_ID",
"input": [
{ "role": "user", "content": "What's the weather in Shanghai right now?" },
{
"type": "function_call",
"call_id": "call_k3Jw9Xq2mZ7tVb1R",
"name": "get_weather",
"arguments": "{\"city\":\"Shanghai\"}"
},
{
"type": "function_call_output",
"call_id": "call_k3Jw9Xq2mZ7tVb1R",
"output": "{\"temp_c\":22,\"condition\":\"cloudy\"}"
}
],
"tools": ["...same as in the first request..."]
}
The tools value in the example is abbreviated; a real request must include the full tool definitions. For a comparison with the Chat Completions format, see Tool calls.
Gateway behavior
- No protocol conversion: the gateway forwards requests to the upstream's
/responsesin the Responses protocol and never converts them to Chat Completions or Anthropic Messages, so both the model and the channel serving it must support Responses natively. When a model declares its supported endpoints and Responses is not among them, the gateway returns 400This model does not support the responses API(unsupported_model_api) directly; models that declare none are forwarded as usual, and the upstream returns the error. - Requests are forwarded mostly as is: the gateway makes only these changes: it replaces
modelwith the selected channel's actual model name; with a virtual model, it merges parameters and the system prompt; it changes item IDs ininputthat start withat_to start withmsg_, for compatibility with upstreams that require themsg_prefix; and it reorders the keys of objects in the request JSON (array order is unchanged) so identical prefixes reliably hit the upstream cache.store,previous_response_id,background,includeand built-in tools are all forwarded as is. - Virtual models: when
instructionsis empty, the virtual model's system prompt fills it; when it already has content, the prompt is appended after a blank line. Parameters are merged using Chat Completions field names, andmax_tokensis not converted tomax_output_tokens; see Virtual models. - Usage:
usagereturns the upstream's values unchanged; the gateway does not fill in or rewrite them. - Fallback on failure: when the upstream returns an error or the connection fails before output starts, the gateway switches channels as described in Retries and fallback, and if needed moves on to fallback models and the workspace's Default Model. The model that takes over must also support Responses, or the
unsupported_model_apierror above is returned. Once streaming output has started, the gateway no longer switches channels. - Codex CLI: setting
wire_api = "responses"in Codex calls this endpoint; the model and channel also need to support function tools. For setup, see Codex. - When quotas are configured, responses carry
x-aigw-quota-*response headers; see Gateway headers and extensions.
Common errors
Errors returned by the gateway take the form {"error":{"message":"...","type":"...","code":"..."}}; only some errors include code. Errors passed through from the upstream unchanged may have a different shape: for example, when the response that previous_response_id points to does not exist, the result is 400 {"request_id":"...","code":"InvalidParameter","message":"..."}, with no outer error. Handle both when parsing.
| Status code | error.message |
error.code |
Cause and fix |
|---|---|---|---|
| 400 | This model does not support the responses API |
unsupported_model_api |
The model does not support Responses. Switch to a model that does, or use Chat Completions instead; if this appears in Codex, switch models as well |
| 400 | you must provide a model parameter |
missing_model |
The request has no model, and the workspace has no Default Model either. Specify a model ID |
| 403 | Model is not available in the model plaza |
model_not_available |
The model is not enabled in the workspace; see Unavailable models |
| 503 | all routed candidates exhausted |
no_route_candidate |
No channel can serve this request, for example because @vmodel/<name> is misspelled and the virtual model does not exist. Check the name; see Unavailable models |
| Upstream status code | Upstream's original message | Upstream's, unchanged | For example, the response that previous_response_id points to does not exist on the current upstream. In the logs, "Status explanation" shows the source as "Upstream" |
- For 401 / 403 authentication issues, see 401 / 403: authentication and access; for 429, see 429: rate limits and quotas.
- 500 / 504 mean the connection to the upstream failed or the wait for the upstream timed out; the response body is an HTML page rather than JSON. See Timeouts and interrupted streams.
- For the full list, see Errors and request identifiers. Gateway responses do not include a request ID. When troubleshooting, find the request in the request logs by API key and time, then copy the Request ID from the top of the log details.