Skip to content

Streaming

Set stream: true to receive tokens as they are generated, as server-sent events ending with data: [DONE].

With "stream": true, POST /chat/completions returns the answer token by token as server-sent events, in the same format as OpenAI. Your users see text appear immediately instead of waiting for the full answer.

Enable streaming

Add stream: true to the request body. Everything else stays the same. With curl, pass -N to disable output buffering.

curl -N https://promptix.tn/api/v1/chat/completions \  -H "Authorization: Bearer $PROMPTIX_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "model": "openai/gpt-4o-mini",    "stream": true,    "messages": [{ "role": "user", "content": "Write a short poem about Djerba." }]  }'

Event format

The response has Content-Type: text/event-stream. Each event is a single line starting with data: followed by a JSON chat.completion.chunk, then a blank line. The stream ends with the literal data: [DONE].

text/event-streamtext
data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-4o-mini","provider":"OpenAI","choices":[{"index":0,"delta":{"role":"assistant","content":"Palm"},"finish_reason":null}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-4o-mini","provider":"OpenAI","choices":[{"index":0,"delta":{"content":" trees lean"},"finish_reason":null}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-4o-mini","provider":"OpenAI","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}]} data: {"id":"gen-1758880023-kQ3vX8mZpL2nR7tW","object":"chat.completion.chunk","created":1758880023,"model":"openai/gpt-4o-mini","provider":"OpenAI","choices":[],"usage":{"prompt_tokens":15,"completion_tokens":48,"total_tokens":63}} data: [DONE]
  • choices[0].delta.content holds the new text. The first chunk also carries delta.role.
  • The chunk with a non-null finish_reason marks the end of the answer (stop, length, tool_calls).
  • Promptix always asks for usage, so the last chunk before [DONE] includes a usage object with the token counts. Its choices array may be empty: guard against it.
  • Every chunk has the same id. Keep it to look up the cost of the request.
  • Tool calls stream as partial delta.tool_calls arguments, as with OpenAI.

Parsing the stream yourself

The OpenAI SDKs parse events for you. Without an SDK, read the body line by line, keep incomplete lines in a buffer, and stop at [DONE].

JavaScript

stream.mjsJavaScript
// Plain fetch (Node 18+, Deno, Bun, edge runtimes): no SDK requiredconst res = await fetch('https://promptix.tn/api/v1/chat/completions', {  method: 'POST',  headers: {    Authorization: `Bearer ${process.env.PROMPTIX_API_KEY}`,    'Content-Type': 'application/json',  },  body: JSON.stringify({    model: 'openai/gpt-4o-mini',    stream: true,    messages: [{ role: 'user', content: 'Write a short poem about Djerba.' }],  }),}); // Errors raised before the stream starts (401, 402, 400) come back as plain JSONif (!res.ok) {  const body = await res.json();  throw new Error(typeof body.error === 'string' ? body.error : body.error?.message);} const reader = res.body.getReader();const decoder = new TextDecoder();let buffer = ''; outer: 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() ?? ''; // keep the incomplete line for the next read   for (const line of lines) {    if (!line.startsWith('data: ')) continue;    const data = line.slice(6);    if (data === '[DONE]') break outer;     const chunk = JSON.parse(data);    if (chunk.error) throw new Error(chunk.error.message);    process.stdout.write(chunk.choices[0]?.delta?.content ?? '');  }}

Python

stream.pyPython
import jsonimport osimport requests with requests.post(    "https://promptix.tn/api/v1/chat/completions",    headers={"Authorization": f"Bearer {os.environ['PROMPTIX_API_KEY']}"},    json={        "model": "openai/gpt-4o-mini",        "stream": True,        "messages": [{"role": "user", "content": "Write a short poem about Djerba."}],    },    stream=True,    timeout=300,) as res:    res.raise_for_status()    for line in res.iter_lines(decode_unicode=True):        if not line or not line.startswith("data: "):            continue        data = line[len("data: "):]        if data == "[DONE]":            break        chunk = json.loads(data)        if "error" in chunk:            raise RuntimeError(chunk["error"]["message"])        if chunk["choices"]:            print(chunk["choices"][0]["delta"].get("content") or "", end="", flush=True)

Errors during a stream

Authentication, validation, balance and key-limit errors happen before streaming starts, so they are returned as a normal JSON response with the matching status code (400, 401, 402). See Errors and limits.

Once the stream has started, the HTTP status is already 200. If the upstream provider fails at that point, Promptix sends one last event with an error object and closes the stream without [DONE]:

text/event-streamtext
data: {"error":{"message":"Streaming failed","type":"stream_error"}}

Treat a stream that ends without [DONE] as incomplete. The OpenAI Python and Node SDKs raise an APIError when they receive this event.

Billing and disconnects

A streamed request costs the same as a non-streamed one: you pay for the prompt and completion tokens reported in the final usage chunk.

Closing the connection does not cancel the generation

If your client disconnects mid-stream, the model keeps generating until it finishes and the tokens it produced are billed. Use max_tokens to bound the cost of long answers.

Streams are served with Cache-Control: no-cache and X-Accel-Buffering: no. If you put a proxy (Nginx, a CDN) in front of your own backend, disable response buffering there too, or tokens will arrive in bursts. A request, including a stream, can run for up to 300 seconds.