Docs

Chat Completions

The OpenAI Chat Completions format, with streaming, tools, structured outputs, reasoning on or off and prompt caching.

POST https://api.interodin.com/v1/chat/completions takes the OpenAI Chat Completions format unchanged. Any OpenAI SDK or compatible tool works once you set the base URL to https://api.interodin.com/v1 and the key to your io-sk- key.

Basic request

curl https://api.interodin.com/v1/chat/completions \
  -H "Authorization: Bearer $INTERODIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "odin-v1",
    "messages": [
      {"role": "system", "content": "You are a concise assistant."},
      {"role": "user", "content": "Name three Norse gods."}
    ]
  }'

Every response carries an x-request-id header. Quote it when you contact support.

Streaming

Set stream: true to receive the reply as server-sent events. Each data: line holds a chat.completion.chunk, with text in choices[0].delta.content. The last chunk always carries usage, even if you don't set stream_options.include_usage. Its choices array is empty. The stream ends with data: [DONE].

curl -N https://api.interodin.com/v1/chat/completions \
  -H "Authorization: Bearer $INTERODIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "odin-v1", "stream": true, "messages": [{"role": "user", "content": "Write a haiku about Odin."}]}'

If your client disconnects, generation stops. You are billed for the input in full plus the output produced up to that point (for a request that is not streaming, an estimate of it).

If the model goes silent for 300 seconds in the middle of a stream, the stream ends with an error chunk whose code is upstream_unavailable. Treat it like any other failed request and retry.

Tools (function calling)

Describe your functions in tools. When the model wants to call one, the reply has finish_reason: "tool_calls" and a tool_calls array. Run the function yourself, append the result as a tool message, and call the API again.

import OpenAI from 'openai'

const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })

const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Get the current weather for a city',
      parameters: {
        type: 'object',
        properties: { city: { type: 'string' } },
        required: ['city'],
        additionalProperties: false,
      },
    },
  },
]

const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
  { role: 'user', content: 'What is the weather in Cluj?' },
]

const first = await client.chat.completions.create({ model: 'odin-v1', messages, tools })
const reply = first.choices[0]!.message
messages.push(reply)

for (const call of reply.tool_calls ?? []) {
  if (call.type !== 'function') continue
  const args = JSON.parse(call.function.arguments) as { city: string }
  const result = { city: args.city, celsius: 14 } // call your real weather API here
  messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) })
}

const second = await client.chat.completions.create({ model: 'odin-v1', messages, tools })
console.log(second.choices[0]?.message.content)

tool_choice accepts "auto" (the default), "none", "required", or {"type": "function", "function": {"name": "..."}} to force one function. Set parallel_tool_calls: false to get at most one call per turn.

Only function tools are kept. Any other tool type, and web_search_options, is removed from the request without an error, so the model never sees it. Web search is not available on Chat Completions: use the Messages or Responses API. A tool_choice that names a removed tool is removed too.

JSON mode and structured outputs

  • response_format: {"type": "json_object"} makes the reply valid JSON. Say in the prompt what shape you want.
  • response_format: {"type": "json_schema", ...} constrains the reply to your JSON Schema. Set strict: true, and set additionalProperties: false on every object.
import OpenAI from 'openai'

const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })

const completion = await client.chat.completions.create({
  model: 'odin-v1',
  messages: [{ role: 'user', content: 'Extract the person: Ana is 31 and lives in Iasi.' }],
  response_format: {
    type: 'json_schema',
    json_schema: {
      name: 'person',
      strict: true,
      schema: {
        type: 'object',
        properties: { name: { type: 'string' }, age: { type: 'integer' }, city: { type: 'string' } },
        required: ['name', 'age', 'city'],
        additionalProperties: false,
      },
    },
  },
})

const person = JSON.parse(completion.choices[0]?.message.content ?? '{}') as { name: string; age: number; city: string }
console.log(person)

Reasoning control

Both models reason before they answer. Reasoning tokens are billed as output tokens and reported in usage.completion_tokens_details.reasoning_tokens.

  • On or off, no levels. Reasoning is either on or off. 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.
  • The reasoning trace comes back in choices[0].message.reasoning, and in delta.reasoning when streaming. Set include_reasoning: false to leave it out of the reply. It is still billed.
import os

from openai import OpenAI

client = OpenAI(base_url="https://api.interodin.com/v1", api_key=os.environ["INTERODIN_API_KEY"])

completion = client.chat.completions.create(
    model="odin-flash",
    messages=[{"role": "user", "content": "Is 1,000,003 prime?"}],
    # reasoning_effort="none" would turn reasoning off on this model.
)
message = completion.choices[0].message
print(getattr(message, "reasoning", None))
print(message.content)
print(completion.usage.completion_tokens_details)

Prompt caching

Caching is automatic. When the start of a request matches a recent request (same system prompt, same tools, same earlier messages), those input tokens are read from the cache and billed at the lower cached-input rate. The usage reports them in usage.prompt_tokens_details.cached_tokens.

To keep one conversation on the same cache, send a stable x-interodin-session-id header, or a prompt_cache_key in the body. Both are scoped to your organization, so they never collide with other customers. If you send both, give them the same value.

import OpenAI from 'openai'

const client = new OpenAI({ baseURL: 'https://api.interodin.com/v1', apiKey: process.env.INTERODIN_API_KEY })

const completion = await client.chat.completions.create(
  {
    model: 'odin-v1',
    messages: [{ role: 'user', content: 'Summarize our plan so far.' }],
    prompt_cache_key: 'conv_7d2a9f',
  },
  { headers: { 'x-interodin-session-id': 'conv_7d2a9f' } },
)
console.log(completion.usage?.prompt_tokens_details?.cached_tokens)

See Prompt caching for how to structure prompts so they hit the cache.

Vision

Image input depends on the model. Odin v1 is text-only. An image_url content part you put in the newest message fails with 400 image_input_not_supported before any tokens are billed. Each model page says whether the model accepts images.

File input (file content parts) is not supported by any model and fails with 400 unsupported_content. Send the file's text instead.

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.
messagesRequired. Roles system, developer, user, assistant, tool.
stream, stream_optionsThe last streamed chunk always includes usage.
max_tokens / max_completion_tokensClamped to the model's maximum output. A value of zero or less fails with 400 invalid_request.
temperature, top_p, stop, seed, presence_penalty, frequency_penaltyStandard sampling controls.
tools, tool_choice, parallel_tool_callsFunction calling. Only function tools are kept.
response_formattext, json_object or json_schema.
reasoning_effort, include_reasoningreasoning_effort: "none" turns reasoning off on Odin Flash. Other values change nothing. include_reasoning: false hides the trace. See Reasoning control.
prompt_cache_keyCache routing hint.
user, safety_identifierYour own end-user id, for tracing and abuse reports.
storeIgnored: nothing is stored, so it is always 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.

The full schema is in the API reference.

On this page