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].
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.contentholds the new text. The first chunk also carriesdelta.role.- The chunk with a non-null
finish_reasonmarks the end of the answer (stop,length,tool_calls). - Promptix always asks for usage, so the last chunk before
[DONE]includes ausageobject with the token counts. Itschoicesarray 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_callsarguments, 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
// 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
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]:
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.