Streaming

Receive tokens as server-sent events and parse them correctly.

Set "stream": true to receive the response as server-sent events. Each event is a line starting with data: followed by a JSON chunk; the stream ends with data: [DONE].

Chunk format

text/event-stream
data: {"id":"chatcmpl-9f2c1a","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant","content":""}}]}

data: {"id":"chatcmpl-9f2c1a","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{"content":"42"}}]}

data: {"id":"chatcmpl-9f2c1a","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{"content":" — according to Douglas Adams."}}]}

data: {"id":"chatcmpl-9f2c1a","object":"chat.completion.chunk","model":"openai/gpt-4o-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":14,"completion_tokens":9,"total_tokens":23}}

data: [DONE]
FieldDescription
choices[0].delta.contentThe new text in this chunk. Append it to your output.
choices[0].finish_reasonSet on the final content chunk (stop, length, …).
usageToken counts, sent on the last chunk.

Parsing SSE manually

SDKs handle this for you. Without one, read the body incrementally, split on newlines and keep any partial line for the next read:

TypeScript (fetch)
const res = await fetch(url, { method: "POST", headers, body });
const reader = res.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() ?? ""; // keep partial line

  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6).trim();
    if (data === "[DONE]") return;
    const chunk = JSON.parse(data);
    process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
  }
}
Errors that happen before streaming starts return a normal JSON error with a status code. Lines starting with : are keep-alive comments — ignore them.