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. Setstrict: true, and setadditionalProperties: falseon 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 v1nonedoes not turn it off: use the Messages API withthinking: {"type": "disabled"}for that. The valuesminimal,low,medium,high,xhighandmaxare accepted but change nothing. - The reasoning trace comes back in
choices[0].message.reasoning, and indelta.reasoningwhen streaming. Setinclude_reasoning: falseto 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
| Parameter | Notes |
|---|---|
model | Required. For example odin-v1. |
messages | Required. Roles system, developer, user, assistant, tool. |
stream, stream_options | The last streamed chunk always includes usage. |
max_tokens / max_completion_tokens | Clamped 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_penalty | Standard sampling controls. |
tools, tool_choice, parallel_tool_calls | Function calling. Only function tools are kept. |
response_format | text, json_object or json_schema. |
reasoning_effort, include_reasoning | reasoning_effort: "none" turns reasoning off on Odin Flash. Other values change nothing. include_reasoning: false hides the trace. See Reasoning control. |
prompt_cache_key | Cache routing hint. |
user, safety_identifier | Your own end-user id, for tracing and abuse reports. |
store | Ignored: nothing is stored, so it is 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.
The full schema is in the API reference.