POST /v1/responses
OpenAI's Responses API: OpenAI models as a passthrough (Codex, the Agents SDK), Claude and other models in preview.
OpenAI's Responses API. Point an OpenAI SDK at https://api.routerplus.com/v1 and call client.responses.create with your marketplace key. Codex (the CLI, the desktop app and the IDE extension), the OpenAI Agents SDK and other tools built on the Responses API call this route. OpenAI's models are served as a passthrough. Claude and the other chat models are a preview here: the gateway translates them through its chat surface. The chat twins of this route are POST /v1/chat/completions and POST /v1/messages.
POST https://api.routerplus.com/v1/responses
POST https://api.routerplus.com/v1/responses/compactWhich models
| Models | Status | How the gateway serves them |
|---|---|---|
gpt-6-astra, gpt-6.1-sol, gpt-6-sol, gpt-6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-4o, gpt-4o-mini, gpt-4.1-mini | Supported | Passthrough to OpenAI's own Responses API, on the marketplace route or on your own OpenAI connection. The body goes as you sent it, except for the fields in Request, and the answer and its stream events come back as OpenAI sent them, with usage.cost added. |
Claude models (claude-sonnet-5-5, claude-opus-5-5, …, and their rp- names) and the other chat models | Preview | Served through the chat surface, with every chat rule: the request is translated to a chat request, and the answer back. See Preview: Claude and other models. |
A deployed model from Optimize (tm/<name>-v<n>) and a dedicated endpoint are not served here: they are a 400 with code model_not_supported_on_responses, whose message points to /v1/chat/completions. Image models answer on POST /v1/images/generations only, and decision models on POST /v1/decisions only. An id that is not in the catalog is an honest 404, never a silent substitute.
Authentication
| Header | Format |
|---|---|
Authorization | Bearer tm_vk_... (what the OpenAI SDK and Codex send) |
x-api-key | tm_vk_... — also accepted, same key |
A missing or invalid key returns 401 with error_type: "auth". None of your request headers reach the provider: the gateway sends its own key, or your connection's.
Request
The body is OpenAI's Responses API body, capped at 10 MB. The gateway changes these fields:
| Field | Handling |
|---|---|
model | A catalog id; the provider gets its own id for the model. |
store | Always false: the gateway keeps no responses, and OpenAI does not keep the response for later retrieval. OpenAI's own retention policy still applies (see Data policy). A store: true is dropped and named in x-tm-dropped-params; leave store out or send false. |
previous_response_id, conversation | Not supported: a 400 invalid_request. Send the whole conversation in input on every turn. To keep the model's reasoning between turns, ask for include: ["reasoning.encrypted_content"] and send the reasoning items back (Codex does both). |
background | true is a 400 invalid_request. Stream long answers instead. |
max_output_tokens | Optional; when you send one, it must be a whole number from 1 to the model's own maximum, or to 32,768 if the model's maximum is lower: OpenAI's Responses API takes a limit above GPT-4o's 16,384, and the model stops at its own maximum. When you send none, the gateway adds none: OpenAI uses the model's own maximum, and the balance hold counts at most 32,768 output tokens of it. A limit that you send is held in full, up to the model's own maximum. |
prompt_cache_key | Your value, else your session id (session_id, or the x-session-id, x-session-affinity or x-claude-code-session-id header). |
safety_identifier, user | On the marketplace route, one hash of your organization and your end-user id goes as safety_identifier, never your raw value (see Sessions and prompt caching). |
service_tier | On the marketplace route, a tier other than auto or default is dropped and named: the catalog prices the default tier. |
temperature, top_p | Dropped and named on GPT-6 and GPT-5.6, which take only their default sampling (1 passes). |
tools | Every type passes: function, custom, namespace, and OpenAI's hosted web_search. On the marketplace route, the hosted tools code_interpreter, image_generation, file_search and mcp, and a stored prompt template, are a 400 that names them: they are billed outside the token price, or they read data kept in an OpenAI account. Your own OpenAI connection takes them all. |
provider | The routing controls of Routing policies; read by the gateway, never forwarded. |
Everything else passes to OpenAI as you sent it: input items of every type (reasoning items with encrypted content, custom tool calls), instructions, tool_choice, parallel_tool_calls, reasoning, text, include, metadata, max_tool_calls and truncation.
Web search on the Responses API is OpenAI's hosted web_search tool, billed at OpenAI's price: $10 per 1,000 calls, in addition to the tokens of the search results. The older web_search_preview tool costs $25 per 1,000 calls on GPT-4o, GPT-4o mini and GPT-4.1 mini. The fee is part of usage.cost; see Pricing & billing.
POST /v1/responses/compact is passed to OpenAI the same way and billed from its usage. It never streams, and it takes only the fields OpenAI's compact endpoint takes; the gateway drops and names the others. GET /v1/responses/{id} and the other stored-response endpoints are a 404: stored-response retrieval is unsupported.
Response
On the supported models the response is OpenAI's response object, as OpenAI sent it, with one addition: usage.cost, the exact charge in USD (see Pricing & billing). Usage is OpenAI's: input_tokens (cache reads and cache writes included), input_tokens_details.cached_tokens, input_tokens_details.cache_write_tokens, output_tokens and output_tokens_details.reasoning_tokens, billed with the same math as every other route. The x-request-id response header gives the id for GET https://api.routerplus.com/v1/generation?id=<x-request-id>, and every call appears in GET https://api.routerplus.com/v1/usage.
With stream: true the events are OpenAI's server-sent events, record for record, in OpenAI's order: response.created, response.in_progress, response.output_item.added, response.output_text.delta, …, and response.completed (or response.incomplete), whose response.usage carries the billed usage and cost. There is no [DONE] on this API.
- The first events wait until the provider sends output, so a provider that fails before that is replaced by the next route without you seeing it.
- If the provider fails after output started, the stream ends with the gateway's own
response.failedevent (codeserver_error, the canonical class inmetadata.error_type), and nothing follows it. Codex retries it. - If you close the stream early, you pay for the input and the output streamed to you so far, as on the chat surfaces (see Pricing & billing).
The other response headers are the same as on POST /v1/chat/completions: x-request-id, x-tm-provider, x-tm-attempts, x-tm-dropped-params and the rest.
Preview: Claude and other models
For a model that OpenAI does not serve (Claude, by its id or its rp- name, and the other chat models), the gateway translates the request into a chat request and serves it with the chat surface's own rules: routing and failover, the output default and its hold, automatic caching on Claude, billing and the ledger. The answer comes back as a response object or as Responses stream events. Codex works this way with Claude models.
| Responses input | Chat form |
|---|---|
instructions and developer messages | System messages |
Message items with input_text, output_text, input_image, input_file and input_audio parts | Chat messages with text, image_url, file and input_audio parts |
function_call and function_call_output items | An assistant tool call and its tool message. A tool output with an image (Codex's view_image) goes into a user message right after the tool messages |
custom tools, custom_tool_call and its output | A function tool with one string parameter, input; the call comes back as a custom_tool_call |
namespace tools | Function tools named <namespace>__<name> (with a short hash when that name is taken or too long); the call comes back under its own namespace and name |
max_output_tokens, reasoning.effort, text.format | max_tokens, reasoning_effort, response_format |
tool_choice, parallel_tool_calls, prompt_cache_key, temperature, top_p | Their chat forms, or as sent |
Hosted tools (web_search, file_search, code_interpreter, image_generation, computer_use_preview, mcp, shell, apply_patch and others), and reasoning items you send back | Dropped and named in x-tm-dropped-params, for example tools[3].web_search and input[*].reasoning. Web search is not mapped to Claude's own search |
Also dropped and named: reasoning.summary (Codex asks for one on every request), a text.verbosity other than medium, store: true, and a truncation, max_tool_calls, service_tier or top_logprobs value that asks for something. Refused with a 400 that names the field: previous_response_id, conversation, prompt, background: true, item_reference items, an uploaded file_id, a file_url, and a part or item type that a chat body cannot carry. With "provider": {"require_parameters": true}, a drop is a 400 too.
The answer:
- Its
modelis the name you sent. Itsidisresp_and the request id; thex-request-idheader carries the request id forGET /v1/generation. - Text comes back as a
messageitem, a tool call as afunction_callorcustom_tool_callitem, and the model's reasoning as areasoningitem with its text. statusisincompletewhen the answer reachedmax_output_tokensor was filtered, andcompletedotherwise.- Usage:
input_tokenscounts cache reads and writes, as on the chat surface, andcostis inside it.
Limits of the preview:
- Claude routes refuse
reasoning.effortandtext.formatwith a 400 that names them. In Codex, do not setmodel_reasoning_effortfor a Claude model. - The Claude 5 models refuse a forced tool choice (
tool_choice: "required", or a named tool) with a 400. Codex sendsauto. - A reasoning item that you send back is dropped, so the model's reasoning is not carried from turn to turn. Morph's and Mistral's routes are the exception: a reasoning item with the model's reasoning text, as this surface answers it, goes back to them as that turn's reasoning and is not dropped.
- Annotations and logprobs are not carried, and a custom tool's input arrives whole when its call ends.
POST /v1/responses/compacttakes OpenAI's models only: for any other model it is a 400 with codemodel_not_supported_on_responses.
Use /v1/chat/completions or /v1/messages for production traffic to Claude.
Errors
Every error uses OpenAI's error envelope, with the canonical class in error.metadata.error_type, as on POST /v1/chat/completions. A provider's refusal carries the provider's own words in the message.
- On OpenAI's models,
error.codeis the canonical class, as on the chat surface. A prompt too long for the model hascode: "context_length_exceeded". - On the preview path,
error.codetakes OpenAI's own names where clients act on them:rate_limit_exceeded(classrate_limit),context_length_exceeded(context_overflow),model_not_found(model_unavailable) andinvalid_prompt(content_policy). A failure in the middle of a stream is aresponse.failedevent, and any other code there isserver_error, which Codex retries. A 400 for a field that the translation wrote names your field:reasoning.effort,text.formatormax_output_tokens.
Codex compacts a conversation when it sees context_length_exceeded. The full table is on Errors.
Examples
curl -N https://api.routerplus.com/v1/responses \
-H "Authorization: Bearer $TM_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"gpt-6-luna","stream":true,"store":false,"max_output_tokens":200,"input":"Say hello in one sentence."}'openai SDK with the base URL swappedimport os
from openai import OpenAI
client = OpenAI(base_url="https://api.routerplus.com/v1", api_key=os.environ["TM_API_KEY"])
resp = client.responses.create(
model="gpt-6-luna",
store=False,
max_output_tokens=200,
input="Say hello in one sentence.",
)
print(resp.output_text)
print(resp.usage) # input, cached, output and reasoning tokens, and cost (USD)openai package, streamedimport OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.routerplus.com/v1",
apiKey: process.env.TM_API_KEY,
});
const stream = await client.responses.create({
model: "gpt-5.6-terra",
store: false,
stream: true,
input: "Count to five.",
});
for await (const event of stream) {
if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
if (event.type === "response.completed") console.log("\n", event.response.usage);
}To set up Codex, see Coding agents.
See also: POST /v1/chat/completions · Errors · Wire compatibility · Coding agents
Markdown source for agents: /docs/api-responses.md · index at /llms.txt