# Streaming

Streaming returns an answer piece by piece while the model writes it, so your app can show text right away instead of waiting for the whole reply. Use it for chat interfaces, for long answers, and for anything that reasons for a while. All three chat endpoints stream: [Chat completions](/en/docs/chat), [Messages](/en/docs/messages) and [Responses](/en/docs/responses).

## Turn on streaming

Set `"stream": true` in the request body. The response has the content type `text/event-stream`, and each event follows the format of the endpoint you called.

| Endpoint | Frame | The stream ends with |
| --- | --- | --- |
| `POST /v1/chat/completions` | `data:` line only | `data: [DONE]` |
| `POST /v1/messages` | `event:` line and `data:` line | the `message_stop` event |
| `POST /v1/responses` | `event:` line and `data:` line | the `response.completed` or `response.failed` event |

Every stream follows the same rules:

- A frame is one event followed by a blank line. The `data:` line holds a JSON object, except the final `data: [DONE]` line of Chat completions, which is not JSON. Check for `[DONE]` before you decode.
- A line that starts with `:` is a comment. kenari sends `: keepalive` when the model has been silent for a while, for example during a long prompt or while it reasons. Ignore these lines. The official SDKs do.
- Errors that happen before the stream starts, such as a bad key, an invalid request, low balance or a rate limit, come back as a normal HTTP error with a JSON body. See [Errors](/en/docs/errors).
- Once the first event is sent, the HTTP status is already 200. A failure after that arrives as an event in the stream, described for each format below.

## Fields and events by API format

| | Chat completions | Messages | Responses |
| --- | --- | --- | --- |
| Answer text | `delta.content` | `text_delta` in `content_block_delta` | `response.output_text.delta` |
| Reasoning | `delta.reasoning` and `delta.reasoning_content`, before the text | a `thinking` block with `thinking_delta` events | a `reasoning` item with `response.reasoning_summary_text.delta` events |
| Function call | `delta.tool_calls`, arguments in pieces | a `tool_use` block with `input_json_delta` events | a `function_call` item with `response.function_call_arguments.delta` events |
| Token usage | `usage` on the last chunk, when token counts are non-zero | `message_start` and `message_delta` | `response.completed` |
| Finish reason | `finish_reason` | `stop_reason` in `message_delta` | `status` in `response.completed` |
| Failure after the start | a chunk with an `error` object, then `[DONE]` | an `error` event | a `response.failed` event |

Reasoning is streamed in all three formats, ahead of the answer text. Only reasoning models produce it, and on some requests a model reasons very little or not at all. On Chat completions you can leave it out of the stream with `"reasoning": {"exclude": true}`. The tokens are still generated and billed. To control how much a model reasons, see [Reasoning](/en/docs/reasoning).

## Chat completions stream

Each chunk is a `chat.completion.chunk` with the same `id` and a `model` that echoes the one you requested.

```bash
curl -N https://kenari.id/v1/chat/completions \
  -H "Authorization: Bearer $KENARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "step-3-7-flash:free",
    "stream": true,
    "messages": [{ "role": "user", "content": "Count from 1 to 3." }]
  }'
```

The `-N` flag turns off curl's buffering, so each event prints as it arrives. The stream looks like this, shortened:

```text
data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"content":"1, 2"},"finish_reason":null}]}

data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{"content":", 3"},"finish_reason":null}]}

data: {"id":"4f1c9a2e7b3d5a60","object":"chat.completion.chunk","created":1790000000,"model":"step-3-7-flash:free","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":14,"completion_tokens":6,"total_tokens":20}}

data: [DONE]
```

- The first chunk carries `"role": "assistant"`. Append every `delta.content` after it to build the answer.
- Reasoning arrives in `delta.reasoning` and `delta.reasoning_content`, with the same text under both names.
- A function call opens with one chunk that has the call's `index`, `id`, `type` and `function.name`, and an empty `arguments`. Later chunks with the same `index` carry pieces of `function.arguments`. Join the pieces, then parse the JSON. The call ends with `finish_reason` set to `tool_calls`. See [Function calling](/en/docs/function-calling).
- The last chunk has an empty `delta`, the `finish_reason`, and `usage` only when the token counts are non-zero. `usage.prompt_tokens_details` is added only when cached tokens are above 0. kenari's own usage chunk does not depend on `stream_options`, so you never need `include_usage`. There is no separate usage chunk with an empty `choices` array.
- When a kenari server tool searched the web, one more chunk with `delta.sources` arrives just before `[DONE]`.
- A failure after the start arrives as a chunk with an `error` object and no `choices`, followed by `[DONE]`:

```text
data: {"error":{"message":"the model's provider interrupted the response before it finished. send the request again as a new request","type":"upstream_error","param":null,"code":"stream_interrupted"}}

data: [DONE]
```

With the OpenAI Python SDK:

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"])

stream = client.chat.completions.create(
    model="step-3-7-flash:free",
    messages=[{"role": "user", "content": "Count from 1 to 3."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print("\ntokens:", chunk.usage.total_tokens)
```

With the OpenAI JavaScript SDK:

```js
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://kenari.id/v1",
  apiKey: process.env.KENARI_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "step-3-7-flash:free",
  messages: [{ role: "user", content: "Count from 1 to 3." }],
  stream: true,
});
for await (const chunk of stream) {
  const text = chunk.choices[0]?.delta?.content;
  if (text) process.stdout.write(text);
}
```

## Messages stream

Each frame has an `event:` line that names the event and a `data:` line with the JSON. The base URL for the Anthropic SDKs is `https://kenari.id`, as described on [Messages](/en/docs/messages).

| Event | Meaning |
| --- | --- |
| `message_start` | Opens the message with its `id`, `model` and a first `usage`. |
| `content_block_start` | Opens the block at `index`. The type is `text`, `tool_use` (with `id` and `name`) or `thinking`. |
| `content_block_delta` | A piece of the block: `text_delta`, `input_json_delta`, `thinking_delta` or `signature_delta`. |
| `content_block_stop` | Closes the block. |
| `message_delta` | `delta.stop_reason` and the final `usage`, with `output_tokens` and the cache fields when present. |
| `message_stop` | The end of the stream. |
| `ping` | A keep-alive from the provider. Ignore it. |
| `error` | The message failed after it started. No `message_stop` follows. |

A short stream:

```text
event: message_start
data: {"type":"message_start","message":{"id":"4f1c9a2e7b3d5a60","type":"message","role":"assistant","model":"step-3-7-flash:free","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"output_tokens":0}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"1, 2, 3"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":14,"output_tokens":6}}

event: message_stop
data: {"type":"message_stop"}
```

- When the model reasons, a `thinking` block comes first, with its text in `thinking_delta` events followed by a `signature_delta`. The signature can be empty. Send the block back unchanged in the next turn.
- A function call is a `tool_use` block whose `input` starts as `{}`. The arguments arrive as `partial_json` strings in `input_json_delta` events. Join them, then parse the JSON when the block closes. The message ends with `stop_reason` set to `tool_use`.
- A failure after the start is an `error` event. Its `error.type` is `api_error` or `invalid_request_error`. Read `error.message` and treat the stream as over.

```text
event: error
data: {"type":"error","error":{"type":"api_error","message":"the model's provider interrupted the response before it finished. send the request again as a new request"}}
```

With the Anthropic Python SDK:

```python
import os
import anthropic

client = anthropic.Anthropic(base_url="https://kenari.id", api_key=os.environ["KENARI_API_KEY"])

with client.messages.stream(
    model="step-3-7-flash:free",
    max_tokens=512,
    messages=[{"role": "user", "content": "Count from 1 to 3."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    message = stream.get_final_message()
    print("\ntokens:", message.usage.output_tokens)
```

With the Anthropic TypeScript SDK:

```ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://kenari.id",
  apiKey: process.env.KENARI_API_KEY,
});

const stream = client.messages.stream({
  model: "step-3-7-flash:free",
  max_tokens: 512,
  messages: [{ role: "user", content: "Count from 1 to 3." }],
});
stream.on("text", (text) => process.stdout.write(text));
const message = await stream.finalMessage();
console.log("\ntokens:", message.usage.output_tokens);
```

## Responses stream

Each frame has an `event:` line and a `data:` line. Every event has a `type` and a `sequence_number` that counts up from 0.

| Event | Meaning |
| --- | --- |
| `response.created` | The first event. Carries the response `id`, `model` and `status` of `in_progress`. |
| `response.output_item.added` | An item starts at `output_index`: `reasoning`, `message` or `function_call`. |
| `response.reasoning_summary_part.added` | The reasoning item opens its one summary part. |
| `response.reasoning_summary_text.delta` | A piece of the reasoning text. |
| `response.reasoning_summary_text.done` | The full reasoning text. |
| `response.reasoning_summary_part.done` | The summary part is complete. |
| `response.output_text.delta` | A piece of the answer text. |
| `response.function_call_arguments.delta` | A piece of the function arguments. |
| `response.function_call_arguments.done` | The full arguments string. |
| `response.output_item.done` | The item is complete and carries its full content. |
| `response.completed` | The last event. Carries the full `output` and `usage`. |
| `response.failed` | The response failed after it started. |

kenari does not send `response.in_progress`, `response.content_part.added`, `response.content_part.done`, `response.output_text.done` or `response.incomplete`. A response cut short by the token limit ends with `response.completed` whose `status` is `incomplete`.

- Reasoning comes as one `reasoning` item before any message or function call. Its text streams through the `response.reasoning_summary_text.delta` events.
- A function call is a `function_call` item. Its `arguments` stream through `response.function_call_arguments.delta`, and `response.function_call_arguments.done` carries the whole string.
- `response.completed` holds the final `usage`, including `input_tokens_details.cached_tokens`.
- A failure after the start is a `response.failed` event, with `response.error.code` and `response.error.message`. Nothing follows it.

```text
event: response.failed
data: {"type":"response.failed","response":{"id":"resp_4f1c9a2e7b3d5a60","status":"failed","model":"step-3-7-flash:free","error":{"code":"stream_interrupted","message":"the model's provider interrupted the response before it finished. send the request again as a new request"}},"sequence_number":4}
```

With the OpenAI Python SDK:

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"])

stream = client.responses.create(
    model="step-3-7-flash:free",
    input="Count from 1 to 3.",
    stream=True,
)
for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
```

With the OpenAI JavaScript SDK:

```js
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://kenari.id/v1",
  apiKey: process.env.KENARI_API_KEY,
});

const stream = await client.responses.create({
  model: "step-3-7-flash:free",
  input: "Count from 1 to 3.",
  stream: true,
});
for await (const event of stream) {
  if (event.type === "response.output_text.delta") process.stdout.write(event.delta);
}
```

## Limits and pitfalls

- A stream cannot be resumed. After an error, send the request again as a new request. The status was already 200 when the stream started, so retry logic that reacts to HTTP errors does not see the failure.
- Do not split the stream on network chunks. One chunk can hold several events or half of one. Read it by frames, or let the SDK do it.
- A function call is not valid JSON until its last piece arrives. Parse it after the block or item closes.
- The browser `EventSource` only sends `GET` requests. Use `fetch` with a readable stream, or an SDK.
- kenari sends `Cache-Control: no-cache, no-transform` and `X-Accel-Buffering: no`. If you put your own proxy in front, turn off response buffering there too, or events will arrive in bursts.
- Use streaming for long answers. A request without it holds the connection open until the whole answer is ready, and a proxy or load balancer between you and kenari may close it first.

## See also

- [Chat completions](/en/docs/chat)
- [Messages](/en/docs/messages)
- [Responses](/en/docs/responses)
- [Reasoning](/en/docs/reasoning)
