## Streaming (Server-Sent Events)

With `stream: true` the model response arrives as a stream of events as tokens are generated, rather than as a single chunk at the end. The first byte reaches the client within seconds regardless of the full response length.

Response headers: `Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`. Each event is a `data: {JSON}\n\n` line, and the terminating event is `data: [DONE]\n\n`.

## Event format

```
data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"delta":{"role":"assistant","content":"CRM"}}]}

data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"delta":{"content":" is"}}]}

data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"finish_reason":"stop","delta":{}}],"usage":{"prompt_tokens":10,"completion_tokens":15,"total_tokens":25}}

data: [DONE]
```

`usage` arrives in the last event before `[DONE]`. Accumulate `delta.content` from all events to assemble the full response text.

## Stream handling example

```javascript
const response = await fetch('https://vibecode.bitrix24.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'bitrix/bitrixgpt-5.5',
    messages: [{ role: 'user', content: 'Tell me about CRM' }],
    stream: true,
  }),
})

const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })
  const lines = buffer.split('\n')
  buffer = lines.pop() ?? ''

  for (const line of lines) {
    if (!line.startsWith('data: ')) continue
    const payload = line.slice(6)
    if (payload === '[DONE]') return
    const chunk = JSON.parse(payload)
    const delta = chunk.choices?.[0]?.delta?.content
    if (delta) process.stdout.write(delta)
  }
}
```

## Choosing a mode under load

In synchronous mode (`stream: false`) the full model response is assembled on the server and sent to the client as a single response — the first byte arrives together with the last. For a model with a long reasoning phase, `bitrix/bitrixgpt-5.5-thinking`, on a long context and with a detailed answer, generation takes tens of seconds. If the client timeout is shorter than the full generation time, the connection closes without receiving a single byte — for example, `cURL error 28: Operation timed out, 0 bytes received`. On short requests generation fits within the timeout and the effect does not appear.

In streaming mode (`stream: true`) the server sends status `200` and the headers right after processing begins, and events flow as tokens are generated, including the reasoning phase. For large requests and reasoning models use `stream: true` — the client timeout on receiving the first byte then does not fire.

Guidelines for the client timeout:

- **Streaming mode (`stream: true`)** — set the timeout for receiving the first event with a margin for the model's reasoning. A reasoning model spends a few seconds before the first token; a value of 30 seconds or more covers this phase with a margin.
- **Synchronous mode (`stream: false`)** — the whole-request timeout must cover the full generation time, not just the network exchange. For a large request to a reasoning model this is tens of seconds. In addition, on a provider error or timeout a synchronous request is retried automatically, and then the final response or error arrives after a few minutes. So values of 9-15 seconds are not enough: set the whole-request timeout with a margin for such retries — a few minutes — or switch to streaming mode, where the timeout on receiving the first byte removes the problem.

## Known specifics

**There is no fallback model in the stream.** On a provider failure a synchronous request is retried with the default model, while in streaming mode the client receives the error in the last event before `data: [DONE]`.

**Errors arrive inside the stream.** Status `200` has already been sent when the platform learns of overload or of an interrupted generation, so a service event of the form `data: { "error": { "code": "pool_exhausted", "retryAfter": 5 } }` arrives before `data: [DONE]`. Read the stream to the end and check the `error` field.

**An incomplete structured response also arrives as an error event.** If the request carried `response_format` and the model did not produce a complete JSON, `data: { "error": { "code": "structured_output_truncated", "message": "...", "type": "invalid_request_error" } }` arrives before `data: [DONE]`. This event has only three fields — `code`, `message` and `type`. The `finishReason`, `param` and `suggestedMaxTokens` fields carried by the synchronous `422` are not present here. The `message` text differs by finish reason: generation was cut off with `finish_reason: "length"` — retrying with a raised `max_tokens` is suggested, the stream ended without a finish reason — the response may be incomplete, the model finished on its own with another reason — there is no parseable JSON and a model without reasoning is recommended. See [guaranteed JSON response](./json.md).

**A stalled response is terminated by an idle timeout.** If the model sends headers but then goes silent mid-response and delivers no new data for longer than the idle window, the platform terminates the call and sends `data: { "error": { "code": "stream_idle_timeout", "type": "server_error", "retryable": true, "retryAfter": <seconds> } }` before `data: [DONE]`. The error is retryable — retry the request, honoring `retryAfter`. A continuous flow of tokens (including reasoning tokens from thinking models) resets the idle timer and is never subject to this timeout.

## See also

- [Create a chat completion](./completions.md)
- [Request limits](./rate-limits.md)
- [Guaranteed JSON response](./json.md)
- [AI Router](/docs/ai)