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?
