Skip to content

Streaming Chat Completions in Real Time with i14 API

The i14 API supports streaming responses via Server-Sent Events (SSE). With streaming enabled, tokens arrive as they’re generated instead of waiting for the full response - making your application feel faster and allowing you to display output progressively as the model thinks.

Add "stream": true to your request body to enable streaming. The API will keep the connection open and push chunks of data as the model produces them.

Use --no-buffer with curl to disable output buffering and see each chunk as it arrives:

macOS and Linux (bash or zsh)
curl https://api.i14.ai/v1/chat/completions \
--no-buffer \
-H "Authorization: Bearer $I14_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "i14-sugarglider-nemotron3-super-120B-v1",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Write a short poem about the ocean." }
],
"stream": true,
"max_tokens": 8192
}'

When streaming is enabled, the API responds with a series of data: lines, each containing a JSON object. Each chunk carries a delta object inside choices[0] rather than a full message object. The content field of the delta holds the next token or tokens in the answer. Sugar Glider (i14-sugarglider-nemotron3-super-120B-v1) thinks first, so the stream starts with chunks that carry reasoning_content instead of content.

Once the model finishes generating, the stream ends with a special sentinel line:

data: [DONE]

Here is a sample of what the raw SSE output looks like (the stream is shortened and the x_i14_perf object is trimmed):

data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [{"index": 0, "delta": {"role": "assistant", "reasoning_content": "Ok"}, "finish_reason": null}]}
data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [{"index": 0, "delta": {"reasoning_content": "ay, t"}, "finish_reason": null}]}
data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [{"index": 0, "delta": {"content": "\n\n*"}, "finish_reason": null}]}
data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [{"index": 0, "delta": {"content": "*Ode"}, "finish_reason": null}]}
data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], "usage": null}
data: {"id": "req_5037a8cd7b5c48db8defd10bef0adb77", "object": "chat.completion.chunk", "created": 1791426846, "model": "i14-sugarglider-nemotron3-super-120B-v1", "choices": [], "usage": {"prompt_tokens": 45, "completion_tokens": 869, "total_tokens": 914, "completion_tokens_details": {"reasoning_tokens": 753}}, "x_i14_perf": {"tokens_per_second": 36.328, "generation_time_s": 23.9208, "time_to_first_token_s": 0.0957}}
data: [DONE]

Parse each data: line as JSON, extract choices[0].delta.content, and append it to your output buffer. Skip any chunk where delta.content is absent or empty.

The OpenAI SDK handles SSE parsing for you. Set stream=True (Python) or stream: true (Node.js) in your request and iterate over the returned stream object to access each content delta.

import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["I14_API_KEY"],
base_url="https://api.i14.ai/v1",
)
with client.chat.completions.create(
model="i14-sugarglider-nemotron3-super-120B-v1",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Write a short poem about the ocean."},
],
stream=True,
max_tokens=8192,
) as stream:
for chunk in stream:
if not chunk.choices:
continue # final chunk carries usage only
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print() # newline after stream ends