# Stream a response

> Read text as it arrives and handle interruptions explicitly.

## Enable streaming

Set `stream` to `true` on a chat-completions request. Router sends ordered server-sent events containing content deltas, followed by a final finish reason, usage, and `[DONE]`.

```python
import os
from uuid import uuid4
from openai import OpenAI

client = OpenAI(
    base_url=os.environ["ROUTER_BASE_URL"],
    api_key=os.environ["ROUTER_API_KEY"],
)

request_id = str(uuid4())
stream = client.chat.completions.create(
    model="gemma3:1b",
    messages=[{"role": "user", "content": "Hello!"}],
    stream=True,
    extra_headers={"Idempotency-Key": request_id, "X-Max-Charge-Credits": "5.000000"},
)

for chunk in stream:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="")
```

## Handle completion and failure

An opened connection is not a completed answer. Track content as it arrives and distinguish a finished response from a failed or interrupted stream.

A normal stream contains:

1. Zero or more chunks with `choices[0].delta.content`.
2. A final chunk with `finish_reason` set to `stop` or `length` and a `usage` object.
3. The terminal `data: [DONE]` marker.

### Event by event

Three common outcomes, event by event:

**Completes**: Content deltas, one final chunk with usage, then the end marker.

```text
data: {"id":"$REQUEST_ID","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]}

data: {"id":"$REQUEST_ID","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"!"},"finish_reason":null}]}

data: {"id":"$REQUEST_ID","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":8,"completion_tokens":2,"total_tokens":10}}

data: [DONE]
```

- `delta`: Append choices[0].delta.content to the reply.
- `delta`: More text for the same reply, in order.
- `final`: finish_reason is stop or length, with provider-reported usage.
- `[DONE]`: The stream finished. Mark the reply complete.

**Fails after text**: Text arrives, then an error event ends the generation.

```text
data: {"id":"$REQUEST_ID","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Hel"},"finish_reason":null}]}

data: {"error":{"message":"Generation or settlement interrupted","type":"server_error","code":"provider_timeout"},"request_id":"$REQUEST_ID"}

data: [DONE]

GET /v1/credits/requests/$REQUEST_ID
```

- `delta`: Text arrives normally at first.
- `error`: Terminal for this generation. Keep the received text marked incomplete; codes vary.
- `[DONE]`: May still follow an error. It does not turn the reply into a success.
- `receipt`: Check the saved outcome and charge before sending another paid request.

**You stop receiving**: Your app closes the connection; the accepted work can continue.

```text
data: {"id":"$REQUEST_ID","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Hel"},"finish_reason":null}]}

controller.abort()

GET /v1/credits/requests/$REQUEST_ID
```

- `delta`: Text arrives normally at first.
- `abort`: Your app stops reading. This is not a durable cancellation.
- `receipt`: Generation and settlement can finish after you disconnect. The receipt shows the result and final charge.

These lines are abbreviated. Real chunks also include `created` and `model`; error codes vary.

> **Preserve partial-response state**
> If a stream fails after producing text, mark the reply incomplete. Do not silently append a different attempt or treat the partial reply as a successful answer.

The owner can inspect `GET /v1/credits/requests/{request_id}` with an application key or signed-in account token even if the stream disconnects. Accepted provider-reported partial usage can still be charged; token counts are not independently verified. The receipt shows the final amount when settlement completes.

Router does not replay a stream. Sending the same `Idempotency-Key` again returns `request_already_exists` (409); use the receipt to resolve the original request.

## Stop receiving a stream

Close or abort the client request when the user stops receiving text. Release your stream reader and show that receiving stopped. Accepted paid work may continue to completion and settlement after disconnect; HTTP abort is not guaranteed durable cancellation. Check the saved credit receipt to resolve the result and charge before starting another paid request.

See [errors and limits](https://router.optimai.network/docs/errors-and-limits.md) for recovery guidance.
