> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pramaan.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming

> Show a chat reply as it is generated.

`llm.chat` can send its reply piece by piece, so your screen shows the text as it is written.
Add one header to the usual call: `Accept: text/event-stream`. The request body is the same.

Send that type alone. A request that lists `application/json` beside it without ranking it
lower (`Accept: application/json, text/event-stream`) gets the plain JSON reply, so a client
that never asked for a stream never receives one. Always read the response's `Content-Type`
before you parse: `text/event-stream` is a stream, `application/json` is the whole answer.

```bash theme={null}
curl -N https://api.pramaan.dev/v1/capabilities/llm.chat \
  -H "Authorization: Bearer $PRAMAAN_API_KEY" \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Say hello."}]}'
```

```text theme={null}
event: text_delta
data: {"text":"Hel"}

event: text_delta
data: {"text":"lo."}

event: usage
data: {"inputTokens":12,"outputTokens":3}

event: done
data: {"finishReason":"stop","result":{"text":"Hello.","toolCalls":[],"finishReason":"stop","usage":{"inputTokens":12,"outputTokens":3}},"price_micro":1000}
```

## With the SDK

```ts theme={null}
import { Pramaan } from "@pramaan/sdk";

const p = new Pramaan({ apiKey: process.env.PRAMAAN_API_KEY! });

let shown = "";
for await (const event of p.stream("llm.chat", {
  messages: [{ role: "user", content: "Say hello." }],
})) {
  if (event.type === "text_delta") {
    shown += event.text;
    render(shown);
  }
  if (event.type === "done") save(event.result); // the whole reply, as p.run returns it
}
```

A failure throws a `PramaanError`, before or during the stream. Leaving the loop early stops
the call.

## Events

| Event | Data | Meaning |
| - | - | - |
| `text_delta` | `{ text }` | The next piece of text. |
| `tool_call_start` | `{ index, id, name }` | A tool call begins. `index` is its place in `result.toolCalls`; `id` is its id there. |
| `tool_call_delta` | `{ index, arguments }` | The next piece of that call's JSON arguments text. |
| `tool_call_end` | `{ index }` | That call's arguments are complete. |
| `usage` | `{ inputTokens, outputTokens }` | Tokens for the turn. |
| `done` | `{ finishReason, result, price_micro }` | The end. `result` is the same result the plain call returns. |
| `error` | `{ error, price_micro }` | The reply failed. `error` is the usual [error object](https://docs.pramaan.dev/guides/errors.md). |

The stream ends after `done` or `error`. Keep `done.result`: it is the checked reply. The
pieces are for display. A line that starts with `:` (`: ping`) is a keep-alive sent every 15
seconds; skip it. The SDK does.

## What to know

* **Tools and JSON output work the same.** Tool arguments and JSON-schema output are checked
  when the reply is complete. If the check fails (the JSON does not match, or the reply was cut
  off at `maxTokens`) you get an `error` event after the pieces, and the reply is charged: you
  received it. The plain call makes the same failure free because it returns nothing.
* **Errors before the first piece** are plain JSON with the usual status (400, 402, 429 and
  so on), not an event stream. Check the status before you read events.
* **Price.** A finished reply costs what the plain call costs; the price is `price_micro` in
  `done` (there is no price header on a stream). If the reply breaks after pieces were sent, or
  you disconnect, you pay for what was generated, never more than the
  [estimate](https://docs.pramaan.dev/guides/pricing.md). When the break leaves no token count, the charge is a ceiling:
  your input at the estimate's bound and one token per byte sent. That can be more than the
  finished reply would have cost, so let a reply finish when you can. A call that sent nothing
  costs nothing. The estimate is held from your credit while the stream is open.
* **Idempotency.** A repeated `Idempotency-Key` returns the stored reply as plain JSON, not a
  stream; while the first is still running it answers `409`. A reply that broke is not stored,
  so the same key runs, and is charged, again.
* **Limits.** A stream is stopped after 120 seconds, or after 50 seconds when the call carries
  an `Idempotency-Key`. An organisation may have 20 streams open at once; one more answers
  `429`. Nothing is sent until the first piece is ready, which can take several seconds.
* **Disconnecting stops the reply.**
* **MCP** returns the whole reply; it does not stream.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.