Responses

API reference for the OpenAI Responses-compatible endpoint

Creates a model response using OpenAI's Responses API shape — the item-based alternative to Chat Completions. Point an OpenAI SDK's baseURL at Sansa and call client.responses.create(...) with no other changes. Sansa auto-routes each request to the best underlying model ("sansa-auto") or calls a specific model directly — same behavior as Completions; see Models for details.

POST /v1/responses

Sansa's Responses endpoint is stateless: nothing is stored server-side between requests. Send the full conversation in input on every call — see Stateless model below.

See the code panel for request and response examples.


Body Parameters

input

Required. A string (one user turn) or an array of items representing the conversation so far. Empty input returns 400.

// Shorthand for a single user message.
input: string

// Or an array of items:
input: InputItem[]

Item types you send:

typePurposeKey fields
message (or omit type and set role)One conversation turnrole, content
function_callEcho back a tool call from a prior responsecall_id, name, arguments
function_call_outputThe result of a tool call, for the model to readcall_id, output
reasoningEcho back a prior reasoning item for continuitysummary, encrypted_content
interface MessageItem {
  type?: "message"; // optional when role is present
  role: "system" | "developer" | "user" | "assistant" | "tool";
  content: string | ContentPart[];
}

interface ContentPart {
  type: "input_text" | "input_image" | "input_file";

  // For "input_text".
  text?: string;

  // For "input_image". Accepts an https URL or a data URI.
  // detail: "low" | "high" | "auto" | "original" (maps to "high").
  image_url?: string | { url: string; detail?: string };
  detail?: string;

  // For "input_file". Only image data URIs are supported
  // (no Files API) — non-image files return 400.
  file_data?: string;
}

developer is accepted as an alias for system. input_audio content parts return 400 — Sansa is text-only on Responses (see Completions for input_audio support on chat).

Function tool round trip and reasoning continuity are covered in their own sections below.

instructions

Optional. A developer/system message that applies to this request only. Prepended ahead of the flattened input.

model

Optional. Same behavior as Completions: "sansa-auto", null, or omitted routes automatically; a catalog model ID calls that model directly; an unknown ID returns 400 invalid_model.

stream

Default: false. If true, the response is delivered as named Server-Sent Events — see Streaming below. This is a different wire format from Chat Completions streaming.

temperature

Default: 1.0. Same semantics as Completions.

top_p

Optional. Same semantics as Completions.

max_output_tokens

Optional. Upper bound on generated tokens, including reasoning tokens. Equivalent to max_completion_tokens on Chat Completions.

tools

Optional. Function tools in the flat Responses shape:

interface FunctionTool {
  type: "function";
  name: string;
  description?: string;
  // A JSON Schema object.
  parameters: object;
  strict?: boolean; // accepted; forwarded where supported
}

Also accepted in tools:

  • web_search / web_search_preview — a hosted tool Sansa executes for you. See Web search.
  • shell, apply_patch, custom — client-executed tools that round-trip like function calls; see Client-executed tools.
  • type: "namespace" wrapping a nested tools array — unwrapped automatically.
  • Other hosted tool types (file_search, code_interpreter, computer_use, image_generation, mcp, tool_search) are accepted and ignored rather than rejected, so a mixed tool array from an existing agent config doesn't 400.

tool_choice

Default: "auto". Same values as Completions ("auto", "none", "required", { type: "function", name: "..." }). { type: "web_search" } maps to "auto".

parallel_tool_calls

Default: true. Same as Completions.

reasoning

Optional. Reasoning configuration.

{
  // "none" through "xhigh". "max" is accepted as an alias for "xhigh".
  effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";

  // Requests a visible reasoning summary. Sansa does not run a separate
  // summarizer — when the model produced plaintext reasoning, it fills
  // `summary`; otherwise the reasoning item's summary is empty.
  summary?: "auto" | "concise" | "detailed";
}

See Reasoning for what each effort level means. On sansa-auto, omitting reasoning entirely still defaults to effort: "high" before routing, same as Completions.

text

Optional. Structured output configuration — equivalent to response_format on Completions.

// Default. Unstructured text.
{ format: { type: "text" } }

// Guaranteed valid JSON, no schema enforcement.
{ format: { type: "json_object" } }

// Structured outputs conforming to a JSON Schema.
{
  format: {
    type: "json_schema",
    name: "...",
    strict: true,
    schema: { ... }
  }
}

include

Optional. Array of strings controlling extra output data. Only "reasoning.encrypted_content" has an effect — see Reasoning.

metadata, session_id, prompt_cache_key, prompt_cache_retention

Same semantics as the equivalent Completions fields. session_id also pins sansa-auto routing for sticky provider-cache behavior across turns — see Smart Routing.

safety_identifier, user

Optional. Accepted for OpenAI SDK compatibility. Used as a fallback source for metadata.end_user_id when neither sansa.end_user_id nor metadata.end_user_id is set (checked in that order).

Accepted, no effect

The following are accepted for compatibility but don't change behavior:

FieldNote
truncation"disabled" is the effective behavior regardless of value; Sansa already clamps via its own budget logic
stream_options.include_obfuscationAccepted; obfuscation payloads are never emitted
text.verbosityAccepted, ignored
service_tierAccepted, ignored; echoed back as null
top_logprobsAccepted, ignored
max_tool_callsAccepted as a no-op — this field counts built-in tool calls on OpenAI; Sansa's function tools are unlimited and client-executed
prompt_cache_optionsAccepted; only a 1h/3600s TTL has an effect

Returns

A response object.

interface Response {
  id: string;          // "resp_..."
  object: "response";
  created_at: number;  // unix timestamp
  status: "completed" | "incomplete" | "failed";
  error: { code: string; message: string } | null;
  incomplete_details: { reason: "max_output_tokens" | "content_filter" } | null;

  // The model that actually served the request.
  model: string;

  output: OutputItem[];

  usage: {
    input_tokens: number;
    input_tokens_details: { cached_tokens: number };
    output_tokens: number;
    output_tokens_details: { reasoning_tokens: number };
    total_tokens: number;
  };

  // Echoed request fields.
  instructions: string | null;
  max_output_tokens: number | null;
  metadata: Record<string, string>;
  parallel_tool_calls: boolean;
  reasoning: { effort: string | null; summary: string | null } | null;
  text: { format: { type: string; [k: string]: unknown } };
  tool_choice: string | object;
  tools: object[];
  top_p: number;
  temperature: number;
  truncation: "disabled";
  user: string | null;
  service_tier: null;

  // Always this shape — Sansa Responses never persists a Response object.
  store: false;
  previous_response_id: null;

  // Sansa routing metadata — same shape as chat's `sansa` object,
  // plus `cost` (settled USD, since `usage.cost` isn't part of the
  // Responses spec). See the Models docs.
  sansa?: {
    routed: boolean;
    routed_model: string | null;
    routing_latency_ms: number | null;
    cost: number;
  };
}

Output items

output holds one entry per item the model produced, in generation order (reasoning, then tool calls, then the assistant message — matching whatever order the underlying model actually emitted).

typeEmitted when
reasoningreasoning was requested, or the model returned reasoning
function_callThe model wants to call a function tool
web_search_callA web_search tool call returned citations
shell_call / apply_patch_call / custom_tool_callA client-executed tool call
messageThe model produced visible text (omitted on tool-only turns)
{
  "id": "rs_...",
  "type": "reasoning",
  "status": "completed",
  "summary": [{ "type": "summary_text", "text": "..." }],
  "content": [],
  "encrypted_content": "..."
}
{
  "id": "fc_call_abc123",
  "type": "function_call",
  "status": "completed",
  "call_id": "call_abc123",
  "name": "get_weather",
  "arguments": "{\"location\":\"Tokyo\"}"
}
{
  "id": "ws_...",
  "type": "web_search_call",
  "status": "completed",
  "action": {
    "type": "search",
    "query": "",
    "sources": [{ "type": "url", "url": "https://example.com/..." }]
  }
}
{
  "id": "msg_...",
  "type": "message",
  "status": "completed",
  "role": "assistant",
  "content": [
    { "type": "output_text", "text": "...", "annotations": [] }
  ]
}

status on the top-level response is "incomplete" when generation hit max_output_tokens (incomplete_details.reason: "max_output_tokens") or a content filter ("content_filter"); otherwise "completed".


Streaming

Set stream: true. Unlike Chat Completions' data:-only chunks, Responses streaming sends named events:

event: response.created
data: {"type":"response.created","sequence_number":0,"response":{...}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_...","output_index":0,"content_index":0,"delta":"Hi"}
  • Every event's data has a type matching the event: line, and a monotonically increasing sequence_number starting at 0.
  • There is no data: [DONE]. The stream ends after the terminal event.
  • usage only appears on the terminal event (response.completed or response.incomplete).

See the code panel for a full streaming example with SSE parsing.

Event reference

EventWhen
response.createdFirst event. status: "in_progress", empty output, null usage
response.in_progressImmediately after created
response.output_item.added / .doneAn output item (message, reasoning, or a tool call) starts / finishes
response.content_part.added / .doneA content part within a message item starts / finishes
response.output_text.delta / .doneIncremental / final assistant text
response.function_call_arguments.delta / .doneIncremental / final JSON for a function call's arguments
response.reasoning_summary_part.added / .doneA reasoning summary part starts / finishes
response.reasoning_summary_text.delta / .doneIncremental / final reasoning summary text
response.completedTerminal. Full response object including output and usage
response.incompleteTerminal, in place of completed, when generation was cut short
response.failedTerminal, only if an error occurs after streaming has already started. response.error is set

Errors before any event is sent are a normal JSON error body (same as non-streaming). Errors after response.created arrive as response.failed — check for that event type instead of relying on HTTP status once the stream has opened.


Tool calling

Round trip is item-based rather than message-based:

  1. Send a request with tools and input.
  2. The model returns one or more function_call items in output, each with a call_id.
  3. Run the function yourself.
  4. Send a new request whose input is: the previous input, plus the function_call item(s) from step 2, plus a function_call_output item per call:
{
  "call_id": "call_abc123",
  "type": "function_call_output",
  "output": "{\"temperature\": 22, \"conditions\": \"sunny\"}"
}

call_id is the join key — pass back the exact value from the function_call item. Include tools on every request, not just the first one. output can be a string or an array of input_text parts (concatenated); image parts in tool output are not supported.

Client-executed tools

shell, apply_patch, and custom tools follow the same round trip as function tools, but with typed item names on both sides: shell_call / apply_patch_call / custom_tool_call from the model, and shell_call_output / apply_patch_call_output / custom_tool_call_output for your results. Sansa passes these through so agent runtimes like Codex can execute them locally — Sansa does not execute them itself.


Pass { "type": "web_search" } (or "web_search_preview") in tools to let the model search the web. Sansa executes this as a hosted tool — you don't implement anything.

  • Billed $0.01 per search that returned citations, in addition to the token cost of citation snippets injected into context. A search that returns nothing is not charged.
  • Results appear as a web_search_call output item plus url_citation annotations on the assistant message's output_text.annotations.
  • On sansa-auto with no other tools, search-only requests route to a search-capable model automatically.

Reasoning

Configure with the reasoning body parameter (see above); effort levels are the same scale as chat — see Reasoning.

When the model returns reasoning, Sansa emits a reasoning output item with a plaintext summary. To carry reasoning state across turns for models that need it, pass include: ["reasoning.encrypted_content"] — the reasoning item then also carries encrypted_content, an opaque blob. Echo it back unmodified on a reasoning input item on your next request:

{
  "type": "reasoning",
  "summary": [{ "type": "summary_text", "text": "..." }],
  "encrypted_content": "..."
}

Don't parse, edit, or store this string beyond the round trip — treat it as opaque. It may be omitted even when requested, depending on the model that served the turn; requests work fine without it, reasoning continuity is a quality improvement, not a requirement.


Stateless model

Sansa's Responses endpoint does not persist responses. Every request must carry the full conversation in input.

Accepted (no-ops): omitted store, store: false, omitted previous_response_id, previous_response_id: null. Successful responses always echo store: false and previous_response_id: null.

Rejected with 400 unsupported_parameter:

FieldReason
store: trueNo stored responses
previous_response_id (non-null)No stored responses to reference
conversationConversations is a separate, unsupported OpenAI product
background: trueNo async job runner
prompt (stored prompt template ID)No prompt store
moderationNo moderation product
context_managementCompaction requires stored conversations

An input item with type: "item_reference" is also rejected — it needs a stored item to resolve — but as 400 invalid_request with param: "input", since it's a malformed input array rather than an unsupported top-level field.

The stored-response endpoints below always return 400 rather than 404, so SDKs get a clear message instead of retrying or mis-diagnosing:

  • GET /v1/responses/{response_id}
  • DELETE /v1/responses/{response_id}
  • GET /v1/responses/{response_id}/input_items
  • POST /v1/responses/{response_id}/cancel
  • POST /v1/responses/compact

Errors

Errors use the same envelope as the rest of the API. Responses-specific cases:

HTTP StatusCodeparamWhen
400unsupported_parameterstore, previous_response_id, conversation, background, prompt, moderation, context_managementSee Stateless model
400invalid_requestinputinput is missing, empty, or contains an unsupported item type
400invalid_requestreasoningreasoning.effort isn't a recognized value
400invalid_requesttexttext.format.type is "json_schema" without a name or schema
400unsupported_parameterinputAn input_audio content part was sent (text-only)

All other error codes — auth, credits, rate limits, invalid model, upstream failures — are identical to Completions; see the full reference in Errors.


Migrating from Chat Completions

Providers, routing, credits, and tool execution are unchanged — Responses is a different request/response shape over the same engine. Field renames:

ResponsesChat Completions
inputmessages
instructionsleading system message
max_output_tokensmax_completion_tokens
text.formatresponse_format
created_atcreated
output[]choices[0].message
statusinferred from finish_reason
usage.input_tokensusage.prompt_tokens
usage.output_tokensusage.completion_tokens
function tool { name, parameters }{ function: { name, parameters } }
function_call item call_idtool_calls[].id
function_call_output item{ role: "tool" } message
content input_text / output_textcontent text
content input_imagecontent image_url

Keys restricted with an API key endpoint allowlist to chat.completions can already call /v1/responses — the allowlist treats chat.completions as implying responses. To restrict a key to Responses only, use the responses slug directly.


Billing

Same model as Completions: cost is estimated and reserved before the request, then recalculated from actual usage and settled (refunded or deducted) after. Failed requests are not charged. Web search adds the $0.01-per-search charge described above on top of token cost. Settled cost is on sansa.cost — Responses does not use usage.cost since that field isn't part of the OpenAI Responses spec.