Responses API
POST /v1/responses, translated onto the same engine as chat completions. Stateless: store and previous_response_id are refused rather than quietly reversing a privacy promise.
The endpoint
POST https://api.multigrid.ai/v1/responses. OpenAI’s newer endpoint, running on the same engine as chat completions: same routing, same failover, same prices, same multigrid block on the response. It is a translation layer and deliberately nothing more — a second inference path would be a second place for a bill to be wrong.
curl https://api.multigrid.ai/v1/responses \
-H "Authorization: Bearer $MULTIGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"instructions": "Answer in one sentence.",
"input": "Why is the sky blue?"
}'Why it is stateless
So both fields are a
400 naming the promise they collide with, rather than a feature that quietly reverses it. Every response reports store: false, so a client can see which it got rather than assuming.Sending the conversation yourself costs one thing: the earlier turns go up with every request, and you pay for those prompt tokens each time. That is true of /v1/chat/completions as well, and it is the trade for us not holding your prompts. The output array from an earlier response can be passed straight back into input, which is the same amount of client code as sending an id.
Input items
input is a string, or the item list. Three item types are implemented, and an item type that is not is a 400 naming it rather than a turn silently dropped out of the middle of a conversation:
- Messages —
{ role, content }, withcontentas a string or typed parts:input_text,input_image(a URL or a data URI, either as a bare string or as{ url }),input_fileandoutput_text. function_callandfunction_call_output, keyed bycall_id, so a tool round trip passes back in exactly the shape it came out.reasoningitems are accepted and dropped. They are not something you wrote, and refusing them would break any client that simply echoes back theoutputarray it was given.
{
"model": "openai/gpt-5.6",
"input": [
{ "role": "user", "content": "What is the weather in Lisbon?" },
{
"type": "function_call",
"call_id": "call_1",
"name": "get_weather",
"arguments": "{\"city\":\"Lisbon\"}"
},
{ "type": "function_call_output", "call_id": "call_1", "output": "17C, clear" }
],
"tools": [
{
"type": "function",
"name": "get_weather",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } }
}
}
]
}The rest of the body
| Fields, and what each becomes | Description |
|---|---|
| instructions | A system turn, prepended to the messages. |
| max_output_tokens | The completion ceiling. Reasoning tokens come out of it — see reasoning. |
| text.format | json_schema or json_object, the same structured output as response_format — including on Claude routes, where it is served as a forced tool call. See structured output. |
| truncation | "auto" becomes transforms: ["middle-out"], because that is what it means: make the input fit. What it did is reported under multigrid.transform. |
| tools | Function tools, flat here and nested for the model. web_search becomes the web plugin this gateway already runs and is charged as one. Any other built-in is refused by name rather than skipped. |
| reasoning | Already the unified shape. reasoning.summary is refused: a written summary of a model’s own reasoning is something we would have to generate, and we do not. |
| metadata | Request tags, on the same limits as everywhere else — 16 keys, values to 256 characters. |
What comes back
A response object whose output is an ordered array: reasoning first when the model produced any, then the message, then any tool calls. usage is renamed rather than recomputed — input_tokens, output_tokens, and the cached and reasoning subsets — so it reconciles against your invoice line for line. A response that ran into max_output_tokens comes back with status: "incomplete" and incomplete_details.reason, not as a success with a truncated answer.
With stream: true you get the typed event stream: response.created, response.output_item.added, response.output_text.delta repeatedly, then response.completed carrying the finished object and its usage. Reasoning arrives as response.reasoning_text.delta. Every event carries a sequence_number so a client can tell a dropped frame from a quiet model.
Something here disagrees with what the API actually did? That is a bug in this page, and worth reporting.
Report it