Skip to main content

Responses

POST /v1/responses implements the OpenAI Responses API. The OpenAI SDKs work against it with a base_url change, and, as everywhere else on MindsHub, any model in the catalog can serve a Responses request, not just OpenAI's. This is also the format OpenAI Codex speaks.

Requests and responses use the Responses shapes throughout: SDK helpers like response.output_text and client.responses.stream() work, function-tool round trips (function_call out, function_call_output back in) work on any model, and turns can be chained by previous_response_id instead of resending the conversation.

Two gaps worth knowing before you start: structured output (text.format) is not implemented anywhere on MindsHub yet — it is reported in X-MindsHub-Dropped-Params rather than honored, so a request asking for a JSON schema gets prose. And OpenAI's separate Conversations API (the conversation parameter) is not supported; it is rejected with a 400.

curl https://api.mindshub.ai/v1/responses \
-H "Authorization: Bearer $MINDSHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "sonnet",
"input": "What is the capital of Australia?"
}'
from openai import OpenAI
import os

client = OpenAI(
base_url="https://api.mindshub.ai/v1",
api_key=os.environ["MINDSHUB_API_KEY"],
)

response = client.responses.create(
model="sonnet",
input="What is the capital of Australia?",
)
print(response.output_text)

Request parameters

ParameterTypeRequiredNotes
modelstringyesA model alias from Models.
inputstring or arrayyesA plain string, or an array of input items. See Input.
instructionsstringnoSystem-level guidance. Becomes a leading system message.
streambooleannoDefault false. See Streaming.
toolsarraynoFunction tools and web_search. See Tools.
tool_choicestring or objectno"auto", "required", "none", or {"type": "function", "name": "..."}. On models that restrict forced choice a named or "required" choice is rewritten rather than rejected; see Chat completions → Tool calling for the per-model table.
max_output_tokensintegernoOutput cap.
reasoningobjectnoeffort has the same semantics as reasoning_effort; summary asks for a summary on the returned reasoning item.
temperature, top_pnumbernoHonored where the target model supports them, dropped where it doesn't. See Parameter adaptation.
storebooleannoDefault true, as on OpenAI. See Conversation state.
previous_response_idstringnoChains this turn onto a stored one. See Conversation state.
includearrayno"reasoning.encrypted_content" carries a reasoning model's thinking across turns; other values are ignored.

Accepted and ignored: text, metadata, background (requests are always served synchronously), and any unknown top-level field. conversation, prompt and context_management are rejected with a 400 naming the field rather than silently ignored — a client that believes we are holding its thread would otherwise stop sending history and lose context on every turn.

Input

input takes a plain string, or an array of items for multi-turn conversations and richer content:

{
"model": "sonnet",
"input": [
{"role": "user", "content": "What's in this image?"},
{"role": "user", "content": [
{"type": "input_text", "text": "Describe the chart."},
{"type": "input_image", "image_url": "https://example.com/chart.png"}
]}
]
}

Supported item content types are input_text, output_text, and input_image. Tool round-trips use function_call and function_call_output items; see Tools.

Roles are system, user, and assistant. Prefer instructions over a system item for system-level guidance; both work.

The response

{
"id": "resp_9f2c1ae0b4d8",
"object": "response",
"created_at": 1785401283,
"status": "completed",
"model": "sonnet",
"output": [
{
"type": "message",
"id": "msg_1c40a2",
"role": "assistant",
"status": "completed",
"content": [
{"type": "output_text", "text": "Canberra.", "annotations": []}
]
}
],
"output_text": "Canberra.",
"usage": {
"input_tokens": 12,
"input_tokens_details": {"cached_tokens": 0},
"output_tokens": 4,
"output_tokens_details": {"reasoning_tokens": 0},
"total_tokens": 16
}
}

Notes on the shape:

  • output_text is the flattened assistant text, the same convenience field the OpenAI SDK exposes. output carries the structured items.
  • output contains message items and, when the model calls tools, function_call items.
  • model echoes back the alias you sent, unlike Chat Completions, which reports the resolved provider ID.
  • input_tokens_details.cached_tokens reports prompt cache reads. Caching is automatic on most of the catalog; the Claude family currently caches only via cache_control breakpoints on Messages.
  • output_tokens_details.reasoning_tokens is always 0. Reasoning bills as output but isn't broken out separately here: output_tokens includes it.

Streaming

Set "stream": true and you get the typed Responses event stream, with event: names matching the payload type:

event: response.created
event: response.in_progress
event: response.output_item.added
event: response.content_part.added
event: response.output_text.delta (repeated)
event: response.output_text.done
event: response.content_part.done
event: response.output_item.done
event: response.completed

The SDK's streaming helper parses this natively:

stream = client.responses.create(
model="sonnet",
input="Count to five.",
stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)

Tool calls stream as response.function_call_arguments.delta events between an output_item.added and output_item.done pair for the function_call item.

Every event carries a monotonic sequence_number, so a client can detect a gap, and output items are strictly sequential: item N is closed before item N+1 opens. Text that resumes after a tool call opens a second message item rather than reopening the first.

The final response.completed event carries the complete response object, including real token usage. Three behaviors to code around:

  • A request that fails before generation starts returns a JSON error, not an SSE stream, even with stream: true. Check the response Content-Type before parsing.
  • A mid-stream failure is reported in-band as a response.failed event, because the HTTP status is already 200 by then. It carries response.error.coderate_limit_exceeded, context_length_exceeded, insufficient_quota, invalid_prompt or server_error — plus whatever output was produced before the failure, marked incomplete. response.failed and response.completed are mutually exclusive.
  • A truncated turn terminates with response.incomplete, not response.completed, with incomplete_details.reason set to max_output_tokens or content_filter.

Tools

Function tools use the Responses flat shape: name and parameters at the top level of the tool object, not nested under function:

{
"model": "sonnet",
"input": "What's the weather in Lisbon?",
"tools": [{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]
}

The model's call arrives as a function_call item in output:

{"type": "function_call", "id": "fc_a91b", "call_id": "call_a91b",
"name": "get_weather", "arguments": "{\"city\": \"Lisbon\"}"}

Return the result by appending both the function_call item and a function_call_output item to input, then calling again:

{
"model": "sonnet",
"input": [
{"role": "user", "content": "What's the weather in Lisbon?"},
{"type": "function_call", "call_id": "call_a91b",
"name": "get_weather", "arguments": "{\"city\": \"Lisbon\"}"},
{"type": "function_call_output", "call_id": "call_a91b", "output": "19°C, light rain"}
],
"tools": [{"type": "function", "name": "get_weather", "description": "...", "parameters": {}}]
}

Echo call_id back exactly as received, and send the same tools array on the follow-up.

web_search maps onto the platform's web search on models where it's available:

{
"model": "sonnet",
"input": "What changed in the EU AI Act this month?",
"tools": [{"type": "web_search"}]
}

On models without search, the tool entry is dropped and the model answers from its own knowledge. Searches carry a per-search charge; see Billing. A forced tool_choice can't be applied to server-side search and is ignored when your tools array holds only web tools.

Conversation state

Responses are stored by default, so you can chain turns by id instead of resending the conversation:

first = client.responses.create(model="sonnet", input="My name is Quill.")
second = client.responses.create(
model="sonnet",
input="What's my name?",
previous_response_id=first.id,
)

Send store=False to keep a turn out of storage entirely; its id is then not retrievable and cannot be chained from. Resending the full conversation in input every turn also remains valid, and is what the OpenAI SDK does when you aren't chaining. Prompt caching keeps that cheap on most models: repeated prefixes bill at roughly a tenth of the input rate.

Stored turns are readable and removable:

CallBehavior
client.responses.retrieve(id)The response as it was returned, output items included.
client.responses.input_items.list(id)That turn's own input items — not the inherited history.
client.responses.delete(id)Hard delete. The content is gone, not flagged.

Three boundaries worth knowing:

  • Stored turns expire after 30 days. After that the id is a 404 and chains through it shorten.
  • A chain is walked at most 25 turns back. Beyond that, and when an older turn has expired or been deleted, the request is still served with the history that remains and the response carries X-MindsHub-Chain-Truncated.
  • An unknown previous_response_id is a 404, not a silently context-free answer.

instructions are per turn and are deliberately not carried across previous_response_id, matching OpenAI — send them again on each request if you want them to persist.

The conversation parameter (OpenAI's separate Conversations API) is not supported and is rejected with a 400.

Parameter adaptation

As on every MindsHub endpoint, parameters the target model doesn't support are dropped rather than rejected, and the response reports what changed:

HeaderMeaning
X-MindsHub-Dropped-ParamsParameters removed for this model, e.g. top_k
X-MindsHub-Clamped-ParamsValues adjusted to the model's range, as name=requested>applied

Neither header appears when nothing was changed. Details and boundaries in Core concepts.

Errors

Errors use the OpenAI envelope, so SDK error handling works unmodified:

{
"error": {
"message": "The model 'foo' does not exist or you do not have access to it.",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}

Status codes and their meanings are shared across the API; see Errors.

Using Codex

Codex speaks this format exclusively, so wire_api = "responses" runs it on any catalog model, including Claude and Kimi. See Coding agents for the config, or MindsHub in Codex for the full guide.