Fluxio
← Back to the blog

Parse OpenAI Token Streams Correctly in Five Minutes

SSE & APIsEN

§1 OpenAI streaming chunks have three separate boundaries

A live AI reply looks like a stream of words, but your application receives several nested formats. The network delivers byte chunks, Server-Sent Events (SSE) frames carry messages, and JSON payloads describe incremental changes. These boundaries do not line up. Parsing each network read as JSON is the fastest way to build a streaming client that fails unpredictably.

This five-minute quick start focuses on OpenAI-style Chat Completions streams. The Responses API uses different event schemas, so do not apply the same delta extractor to both endpoints.

§2 Minute 1: Request a streaming Chat Completions response

Send a POST request to your provider's /v1/chat/completions endpoint with your authorization header, a model identifier, your messages, and stream: true. Check the HTTP status before reading the body. An authentication failure or invalid request may return an ordinary JSON error instead of an SSE stream.

Fluxio fits here when you want a streaming-first, pay-per-token relay with one key across multiple model providers. Its API base is https://api.fluxioapi.com; configure OpenAI-style clients with https://api.fluxioapi.com/v1. Select an available model, such as Gemini 2.5 Flash, using its exact API identifier from the model catalog.

For the shortest implementation, use an OpenAI SDK's streaming iterator. The SDK handles transport framing; your application still needs to interpret deltas, termination, and errors correctly. The remaining steps explain those responsibilities and the raw-fetch equivalent.

§3 Minute 2: Decode bytes before splitting SSE events

With raw fetch, read the response body incrementally and keep one UTF-8 decoder alive for the entire response. In JavaScript, call decoder.decode(bytes, { stream: true }) for each read. This preserves characters whose bytes arrive in different network chunks. Flush the decoder when the byte stream ends.

Append decoded text to a persistent buffer. Parse lines incrementally, retaining unfinished lines between reads. Handle both LF and CRLF line endings, including a CRLF pair split across reads. A blank line dispatches the accumulated SSE event; a network read does not.

  • – Ignore comment lines beginning with a colon.
  • – Collect data fields and join multiple data lines with a newline.
  • – Remove only the optional single space following the field colon.
  • – Keep event metadata separate from the JSON data payload.

A tested SSE parser is usually safer than a quick split on double newlines. Never assume that one read contains one event, or that an event contains one model token.

§4 Minute 3: Append delta content, not whole chunks

For every dispatched data payload, check for [DONE] before calling JSON.parse. It is a stream sentinel, not valid JSON. Otherwise, parse the payload and inspect its choices array.

For a basic single-choice chat interface, append chunk.choices?.[0]?.delta?.content ?? '' to your displayed answer. A chunk may contain only a role, an empty delta, or metadata, so missing content is normal. If you request multiple choices, maintain separate output buffers keyed by each choice's index.

Do not concatenate serialized JSON objects or replace the displayed answer with each delta. Deltas are incremental additions, not complete snapshots. Render accumulated text as text rather than inserting untrusted model output directly as HTML.

§5 Minute 4: Separate tool calls, usage, and completion

Tool-call fragments belong in a separate accumulator keyed by choice index and tool-call index. Append function argument fragments until that call is complete, then parse the assembled argument string. Individual fragments are often invalid JSON.

A non-null finish_reason marks a choice as finished, but it does not necessarily mean the transport has ended. Continue consuming events through the stream terminator. When supported and requested, a final usage chunk may have an empty choices array. Record it without trying to extract text.

§6 Minute 5: Test truncation and cancellation

Test your parser with JSON split across reads, several events in one read, split UTF-8 characters, empty deltas, and usage-only chunks. Also simulate a dropped connection before normal termination. Preserve partial text, but label the reply interrupted rather than complete.

Use AbortController for a stop button, surface streaming errors, and avoid silently retrying after displaying partial output: retries can duplicate text and incur additional charges.

Sign up for Fluxio.

Create a Fluxio account →

More from the current