Skip to main content
Ottili AI API

Ottili AI API — Streaming

How to stream Ottili AI completions with server-sent events on the public, OpenAI-compatible surface.

The Ottili AI API supports streaming responses so tokens arrive as they are generated.

Streaming is enabled per request with "stream": true on the OpenAI-compatible

POST /v1/chat/completions surface.

The full streaming contract (proxy compatibility, deterministic ordering, disconnect cancellation, backpressure, heartbeat policy, usage finalization, error events, no-leak sanitization) is part of the public API contract documented in this reference. This page is the fastest path.

Request

curl -N -X POST "https://api.ai.ottili.one/v1/chat/completions" \
  -H "Authorization: Bearer otk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ottili/vale-1.2",
    "stream": true,
    "messages": [{ "role": "user", "content": "Draft a follow-up email." }]
  }'

Tenant context is resolved from the API key — you do not* send X-Platform-Company

or any company_id header (raw company-ID headers are ignored on the public surface).

Response format

The response uses text/event-stream. Each event is a data: line carrying a

JSON delta in the OpenAI chat.completion.chunk shape, ending with data: [DONE]:

data: {"id":"chatcmpl_ottili_x","object":"chat.completion.chunk","created":1783987300,"model":"ottili/vale-1.2","choices":[{"index":0,"delta":{"role":"assistant"}}]}

data: {"id":"chatcmpl_ottili_x","object":"chat.completion.chunk","created":1783987300,"model":"ottili/vale-1.2","choices":[{"index":0,"delta":{"content":"Hi"}}]}

data: {"id":"chatcmpl_ottili_x","object":"chat.completion.chunk","created":1783987300,"model":"ottili/vale-1.2","choices":[{"index":0,"delta":{"content":" there"},"finish_reason":"stop","usage":{"prompt_tokens":12,"completion_tokens":8,"total_tokens":20}}]}

data: [DONE]

The terminal chunk carries finish_reason and* usage before [DONE], so billing

settles even on a mid-stream disconnect. When the upstream did not report exact

token counts, usage.source is "estimated" and cache/reasoning detail fields are

omitted (never invented).

Consuming in code

const res = await fetch("https://api.ai.ottili.one/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OTTILI_AI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ model: "ottili/vale-1.2", stream: true, messages }),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("\n")) {
    if (!line.startsWith("data:") || line.includes("[DONE]")) continue;
    const chunk = JSON.parse(line.slice(5));
    if (chunk.choices?.[0]?.delta?.content) process.stdout.write(chunk.choices[0].delta.content);
  }
}

Tool calls and structured outputs can also be streamed; see their references. If streaming

is unsupported for the selected model, the API returns a clear error (capability_unsupported,

HTTP 400) rather than a partial body. Client disconnect aborts the upstream pull

(no buffering); the partial result settles exactly once.

Was this article helpful?