Docs

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:

  • store is always false. Whatever you send, nothing is kept after the reply.
  • previous_response_id is not supported. Requests that set it fail with 400 unsupported_parameter. Instead, send the whole conversation in input, as shown in Multi-turn conversations.
  • There is no GET or DELETE /v1/responses/{id}. They return 404 not_found.
  • conversation and background: true are not supported either, and fail with 400 unsupported_parameter.
  • Stored prompt templates (prompt) and item_reference input items fail with 400 invalid_request.
  • metadata is 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:

EventMeaning
response.createdThe response started.
response.output_item.addedA new output item (message, function call, reasoning) began.
response.output_text.deltaA piece of text, in delta.
response.function_call_arguments.deltaA piece of a tool call's JSON arguments.
response.output_item.doneAn output item is complete.
response.completedDone. response.usage holds the token counts.
response.incompleteDone, but cut short, for example by max_output_tokens. response.usage holds the token counts.
errorGeneration 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.

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

ParameterNotes
modelRequired. For example odin-v1.
input, instructionsA string or a list of input items.
max_output_tokensClamped to the model's maximum output. A value of zero or less fails with 400 invalid_request.
prompt_cache_keyCache routing hint.
user, safety_identifierYour own end-user id, for tracing and abuse reports.
storeAlways false.
metadataIgnored 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.

On this page