← Blog

Claude Streaming Output: Patterns That Actually Work

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

Claude streaming output is the token-by-token delivery of a model's response over a persistent HTTP connection, using server-sent events instead of waiting for the full completion before sending anything back. If you've searched for this, you're probably trying to decide when to use it, how to consume it correctly in your frontend or backend, or why your current streaming implementation feels janky.

This article skips the "what is SSE" basics and goes straight into the patterns that matter once you're building something real: chat UIs that don't stutter, backend proxies that don't buffer accidentally, tool calls that interrupt the stream, and error handling when the connection drops halfway through a response.

Why stream at all

Streaming exists to solve one problem: perceived latency. A non-streamed Claude response for a 2,000-token answer might take 15-20 seconds to arrive. With streaming, the user sees the first words in under a second and reads along as the rest generates. Total wall-clock time is similar or slightly higher, but the experience is night and day.

Streaming is worth the extra implementation complexity when:

It's usually not worth it when:

Consuming the stream correctly

The most common mistake is treating a stream like a single fetch response. You need to read the response body as a stream of chunks and parse SSE events as they arrive, not wait for the connection to close.

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-5",
    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 = line.slice(6);
    if (payload === "[DONE]") continue;

    const event = JSON.parse(payload);
    if (event.type === "content_block_delta") {
      process.stdout.write(event.delta.text);
    }
  }
}

The buffer-and-split pattern matters because chunks from the network don't align with SSE event boundaries — a single data: line can arrive split across two read() calls. Skipping this is why some streaming implementations occasionally drop or garble the last few characters of a sentence.

Rendering without jank

On the frontend, don't call setState on every single delta if you're rendering markdown or code blocks — re-parsing markdown on every token is expensive and causes visible flicker as headings and code fences pop in and out. Batch deltas into a small buffer and flush every 30-50ms instead:

let pending = "";
let scheduled = false;

function onDelta(text) {
  pending += text;
  if (!scheduled) {
    scheduled = true;
    requestAnimationFrame(() => {
      renderMarkdown(pending);
      scheduled = false;
    });
  }
}

This keeps the UI smooth even on fast connections where deltas arrive every few milliseconds.

Tool use mid-stream

If your requests include tools, streaming gets more involved: Claude can emit a content_block_start for a tool call, stream the input as JSON deltas, then stop the stream so your application can execute the tool and send results back in a follow-up request. Your parser needs to distinguish between text deltas and tool-input deltas and accumulate the JSON fragments correctly before trying to parse them — parsing a partial JSON object will throw. See /docs/tools for the full event sequence and /docs/streaming for the event type reference if you're building against SubToAPI.

Handling disconnects and partial responses

Long streams over flaky networks will occasionally drop mid-response. Two things to build for this:

  1. Track what you've received. Keep the accumulated text and the last event index so you can show the user a partial answer instead of nothing, and log where the cut happened.
  2. Don't auto-retry the whole request blindly. Retrying a streaming call re-runs the entire generation and doubles your token usage. If partial output is usable, let the user decide whether to regenerate.

If you're proxying Claude through your own backend before it reaches the browser, make sure nothing in that path buffers the response — some reverse proxies and serverless runtimes buffer output by default, which silently turns your "streaming" endpoint into a slow non-streaming one. Disable buffering explicitly or use a runtime built for long-lived connections.

Where SubToAPI fits

SubToAPI turns your existing Claude access into a standard HTTPS API with application keys (sub_live_...), so streaming, tool use, and usage metadata work the same way regardless of which app or environment is calling it. If you're prototyping the patterns above, /docs/quickstart gets you a working streaming call in a few minutes, and /docs/messages covers the request and response shapes in detail. Plans start at €9/month with a free trial at /signup — see /pricing for team and scale tiers if you're rolling this out across multiple services or seats.

Questions

Does streaming change how many tokens I'm billed for? No. Streaming changes delivery timing, not token count. You're billed for the same input and output tokens whether the response arrives all at once or incrementally.

Can I cancel a Claude stream once it's started? Yes — closing the connection or aborting the fetch request stops further token generation on most implementations, which is useful for "stop generating" buttons in chat UIs.

Why does my streamed output arrive in bursts instead of smoothly? This is usually a buffering issue somewhere in the path — a proxy, load balancer, or serverless function buffering the response before forwarding it. Check that every hop between Claude and the client supports unbuffered streaming.

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 →