← Blog

Claude Streaming Response: A Developer's Implementation Guide

2026-09-14 · 5 min read · SubToAPI Team

A Claude streaming response delivers the model's output incrementally, as a sequence of small events sent over an HTTP connection, instead of making you wait for the entire completion to finish before you see anything. This matters for any product where perceived latency affects usability — chat interfaces, coding assistants, voice agents — because users start reading the first words within a few hundred milliseconds rather than waiting several seconds for a long answer to complete.

Technically, streaming works through server-sent events (SSE). When you set "stream": true in a request, the API keeps the HTTP connection open and pushes a series of event:/data: pairs as the model generates tokens. Your client reads this stream line by line, reassembles the text deltas, and renders them as they arrive. This article focuses on the practical side: what the event types actually contain, how to parse them correctly, and the mistakes that break streaming in production.

What a streaming response actually contains

A typical Claude streaming session emits a fixed sequence of event types:

Each content_block_delta event contains a small JSON payload with a text field (for text blocks) or a partial_json field (for tool use blocks being streamed). Your parsing logic needs to accumulate these fragments in order — they are not guaranteed to arrive as whole words or complete JSON objects.

Parsing the stream correctly

The most common bug is treating each SSE data: line as a complete, independent message. In reality, deltas are fragments that must be concatenated. Here's a minimal parser pattern in JavaScript:

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4",
    max_tokens: 1024,
    stream: true,
    messages: [{ role: "user", content: "Explain event loops in Node.js" }]
  })
});

const reader = response.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 incomplete line for next chunk

  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const payload = JSON.parse(line.slice(6));
    if (payload.type === "content_block_delta") {
      process.stdout.write(payload.delta.text ?? "");
    }
  }
}

Two details matter here: you must buffer partial lines across chunk boundaries (network reads don't align with SSE message boundaries), and you must check the type field before assuming a text property exists, since delta payloads differ between text blocks and tool-use blocks.

Handling tool use inside a stream

When Claude calls a tool mid-response, the stream interleaves a content_block_start with type: "tool_use", followed by content_block_delta events carrying partial_json fragments that build up the tool's input object incrementally. You need to accumulate those fragments and JSON.parse the result only once the block closes with content_block_stop — parsing partial JSON mid-stream will throw. If your application both streams text to a UI and executes tool calls, keep separate accumulators per content block index rather than a single global buffer.

Error handling and reconnection

Streaming connections can drop mid-response due to proxies, load balancers, or client-side network changes. A few practical rules:

Streaming vs. non-streaming: when to use which

Streaming is the right default for anything user-facing and conversational. It's usually the wrong choice for batch jobs, background summarization, or any pipeline where you need the complete, validated output before doing something else with it — buffering a full stream just to reassemble it defeats the purpose and adds parsing complexity for no UX benefit.

If you're building on top of Claude through SubToAPI, streaming works the same way over the standard /v1/messages endpoint — you get the same SSE event structure, plus usage metadata per request visible in your dashboard, so you can track token consumption across streamed and non-streamed calls without instrumenting it yourself. See the streaming docs and the messages API reference for the full event schema, or the quickstart if you're setting up your first sub_live_ key.

Practical checklist before shipping

Questions

Does streaming change how tokens are billed? No. Token usage is calculated the same way whether a response is streamed or returned in full — streaming only changes delivery timing, not the number of input or output tokens counted.

Can I cancel a stream partway through? Yes. Closing the underlying HTTP connection (e.g., calling .abort() on the fetch controller) stops generation on the client side. Depending on the provider, tokens generated up to that point may still be counted.

Why does my proxy or CDN break streaming? Many reverse proxies buffer responses by default, which delays or batches SSE chunks instead of forwarding them immediately. You typically need to disable response buffering for the streaming endpoint specifically (e.g., X-Accel-Buffering: no on Nginx).

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →