Skip to content

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.

6 min read

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.

Request
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

store and previous_response_id are refused
The point of the Responses API is that the server keeps the conversation, so the client does not have to. Keeping the conversation means writing prompts and completions to a database, and operations tells you, in as many words, that yours are never written down.

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 }, with content as a string or typed parts: input_text, input_image (a URL or a data URI, either as a bare string or as { url }), input_file and output_text.
  • function_call and function_call_output, keyed by call_id, so a tool round trip passes back in exactly the shape it came out.
  • reasoning items are accepted and dropped. They are not something you wrote, and refusing them would break any client that simply echoes back the output array it was given.
A tool round trip
{
  "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 becomesDescription
instructionsA system turn, prepended to the messages.
max_output_tokensThe completion ceiling. Reasoning tokens come out of it — see reasoning.
text.formatjson_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.
toolsFunction 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.
reasoningAlready 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.
metadataRequest 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