Responses
The OpenAI Responses format, stateless, with streaming events, tools, structured outputs and reasoning control.
POST https://api.interodin.com/v1/responses takes the OpenAI Responses format. The Codex CLI and newer OpenAI SDK features use it.
Stateless by design
InterOdin never stores API content, so Responses works statelessly:
storeis alwaysfalse. Whatever you send, nothing is kept after the reply.previous_response_idis not supported. Requests that set it fail with400 unsupported_parameter. Instead, send the whole conversation ininput, as shown in Multi-turn conversations.- There is no
GETorDELETE /v1/responses/{id}. They return404 not_found. conversationandbackground: trueare not supported either, and fail with400 unsupported_parameter.- Stored prompt templates (
prompt) anditem_referenceinput items fail with400 invalid_request. metadatais ignored. It is removed from the request and not forwarded.
Everything else works as usual: text and message input, streaming, function tools, structured outputs and reasoning control.
Basic request
curl https://api.interodin.com/v1/responses \
-H "Authorization: Bearer $INTERODIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "odin-v1",
"instructions": "You are a concise assistant.",
"input": "Name three Norse gods."
}'output_text is an SDK convenience that joins the text of all message items in output. Every response carries an x-request-id header. Quote it when you contact support.
Multi-turn conversations
Keep the conversation on your side. After each reply, append the model's message and function_call output items to your input, then append the next user message. Leave out reasoning items: nothing is stored server-side, so they can't be referenced later.
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })
const input: OpenAI.Responses.ResponseInput = [{ role: 'user', content: 'Pick a number from 1 to 10.' }]
const first = await client.responses.create({ model: 'odin-v1', input, store: false })
for (const item of first.output) {
if (item.type === 'message' || item.type === 'function_call') input.push(item)
}
input.push({ role: 'user', content: 'Now double it.' })
const second = await client.responses.create({ model: 'odin-v1', input, store: false })
console.log(second.output_text)Keep the earlier part of input identical between turns. That way prompt caching keeps the repeated history cheap.
Streaming
With stream: true you receive typed server-sent events. The ones you'll use most:
| Event | Meaning |
|---|---|
response.created | The response started. |
response.output_item.added | A new output item (message, function call, reasoning) began. |
response.output_text.delta | A piece of text, in delta. |
response.function_call_arguments.delta | A piece of a tool call's JSON arguments. |
response.output_item.done | An output item is complete. |
response.completed | Done. response.usage holds the token counts. |
response.incomplete | Done, but cut short, for example by max_output_tokens. response.usage holds the token counts. |
error | Generation failed mid-stream. |
If the model goes silent for 300 seconds in the middle of a stream, the stream ends with an error event whose code is upstream_unavailable. Treat it like any other failed request and retry.
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.interodin.com/v1", api_key=os.environ["INTERODIN_API_KEY"])
stream = client.responses.create(model="odin-v1", input="Count to five.", stream=True, store=False)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type in ("response.completed", "response.incomplete"):
print("\n", event.response.usage)Tools (function calling)
Function tools are flat in Responses: name, description and parameters sit at the top level of the tool. When the model calls one, output contains a function_call item. Send it back in input together with a function_call_output item that carries your result.
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })
const tools: OpenAI.Responses.Tool[] = [
{
type: 'function',
name: 'get_weather',
description: 'Get the current weather for a city',
parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'], additionalProperties: false },
strict: true,
},
]
const input: OpenAI.Responses.ResponseInput = [{ role: 'user', content: 'What is the weather in Cluj?' }]
const first = await client.responses.create({ model: 'odin-v1', input, tools, store: false })
for (const item of first.output) {
if (item.type !== 'function_call') continue
const args = JSON.parse(item.arguments) as { city: string }
input.push(item)
input.push({ type: 'function_call_output', call_id: item.call_id, output: JSON.stringify({ city: args.city, celsius: 14 }) })
}
const second = await client.responses.create({ model: 'odin-v1', input, tools, store: false })
console.log(second.output_text)Only function tools and the web search tool are kept. Other hosted tools such as file search, code interpreter and MCP are removed from the request without an error, so the model never sees them. A tool_choice that names a removed tool is removed too.
Web search
Add { "type": "web_search" } to tools and the model can search the web and cite its sources. Each search appears as a web_search_call item in output.
- Price. Each search is billed on top of the tokens it uses. The per-search price is on the pricing page on the InterOdin website.
- Limit. A request is billed for the searches it runs; your balance must cover the tokens and up to 5 searches before it starts, or it fails with
402 insufficient_credits. - Where. Web search works on this API and on Messages, not on Chat Completions.
Structured outputs
Use text.format with a JSON Schema:
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })
const response = await client.responses.create({
model: 'odin-v1',
input: 'Extract the person: Ana is 31 and lives in Iasi.',
text: {
format: {
type: 'json_schema',
name: 'person',
strict: true,
schema: {
type: 'object',
properties: { name: { type: 'string' }, age: { type: 'integer' } },
required: ['name', 'age'],
additionalProperties: false,
},
},
},
store: false,
})
console.log(JSON.parse(response.output_text)){"type": "json_object"} is also accepted, for free-form JSON.
Reasoning control
Both models reason before they answer. The reasoning arrives as a reasoning item in output, and its token count is in usage.output_tokens_details.reasoning_tokens. Reasoning tokens are billed as output.
Reasoning is either on or off, with no levels. reasoning: { effort: "none" } turns it off on Odin Flash, which makes answers much faster and cheaper but weaker on arithmetic and logic. On Odin v1 none does not turn it off: use the Messages API with thinking: {"type": "disabled"} for that. The values minimal, low, medium, high, xhigh and max are accepted but change nothing.
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })
const response = await client.responses.create({
model: 'odin-flash',
input: 'Is 1,000,003 prime?',
reasoning: { effort: 'none' },
store: false,
})
console.log(response.output_text)
console.log(response.usage?.output_tokens_details.reasoning_tokens)Prompt caching
Caching is automatic. Send a stable x-interodin-session-id header, or a prompt_cache_key, for each conversation, and read usage.input_tokens_details.cached_tokens. Both are scoped to your organization, so they never collide with other customers. See Prompt caching.
Vision
Image input depends on the model. Odin v1 is text-only. An input_image part in the newest item fails with 400 image_input_not_supported. Each model page says whether the model accepts images.
File input (input_file parts) is not supported by any model and fails with 400 unsupported_content when it is in the newest item.
An image or file that came from a tool result or from an earlier turn of the conversation (for example a file a coding agent read) is replaced by a short text note, so one image in the history cannot break every later message.
Parameters
| Parameter | Notes |
|---|---|
model | Required. For example odin-v1. |
input, instructions | A string or a list of input items. |
max_output_tokens | Clamped to the model's maximum output. A value of zero or less fails with 400 invalid_request. |
prompt_cache_key | Cache routing hint. |
user, safety_identifier | Your own end-user id, for tracing and abuse reports. |
store | Always false. |
metadata | Ignored and not forwarded. |
prompt_cache_key, user and safety_identifier take any string up to 1024 characters. A value of any other type fails with 400 invalid_request. They are scoped to your organization: the API forwards a hash that is specific to your organization, never the value you sent. Use an opaque id, such as a database id, rather than an email address.