← Blog

Claude Streaming: How It Works and How to Set It Up

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

Claude streaming is the mechanism that lets you receive a model's response token by token, as it's generated, instead of waiting for the full completion. If you've ever used the Claude web app and watched text appear word by word, that's streaming in action — and the same capability is available through the API using server-sent events (SSE).

This matters for anything user-facing: chat interfaces, coding assistants, live search results. Without streaming, a user staring at a blank screen for 8–15 seconds while Claude generates a long answer feels broken. With streaming, the first tokens appear in under a second and the perceived latency drops dramatically, even though total generation time is the same.

How Claude Streaming Actually Works

When you set "stream": true in a request, instead of getting one JSON response back, the API keeps the HTTP connection open and sends a sequence of events as the model generates output. Each event is a small JSON payload describing what just happened: a content block started, a text delta arrived, the message stopped, usage stats are final, and so on.

A typical event sequence looks like this:

event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","role":"assistant",...}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":", world"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":12}}

event: message_stop
data: {"type":"message_stop"}

Your client reads these events one at a time and appends each text_delta to whatever buffer or UI element is rendering the response. There's no polling involved — it's a single long-lived HTTP connection.

Streaming with curl

The simplest way to see streaming in action is a raw curl request against an Anthropic-compatible endpoint:

curl -N https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4",
    "max_tokens": 1024,
    "stream": true,
    "messages": [
      {"role": "user", "content": "Write a haiku about databases."}
    ]
  }'

The -N flag disables curl's output buffering so you see events arrive as they're generated rather than all at once at the end. This is a good sanity check before wiring streaming into an application — if events don't trickle in with -N, something upstream (a proxy, a load balancer) is buffering the response.

Streaming in JavaScript

In a browser or Node environment, you typically consume the SSE stream with fetch and read the response body incrementally:

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 one paragraph." }]
  })
});

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);
    }
  }
}

This pattern — read chunks, split on newlines, parse data: lines, handle content_block_delta — is the core of every streaming client, whether you're building a CLI tool, a chat widget, or a backend relay. Most official SDKs wrap this logic in a helper (client.messages.stream(...)) so you don't have to hand-roll the parser, but understanding what's happening underneath makes debugging much easier when something goes wrong.

When to Use Streaming (and When Not To)

Streaming is the right choice for:

Streaming is usually unnecessary for:

Handling Errors Mid-Stream

One thing that trips people up: errors can occur after streaming has already started. You might get a clean message_start event and then, partway through, an error event if the connection drops or a rate limit kicks in mid-generation. Always wrap your stream-reading loop in error handling that can gracefully close the UI state (e.g., show "response interrupted, retry?") rather than leaving a spinner running forever.

Also account for stop_reason values like max_tokens — the stream ends normally, but the content is truncated. Your client should detect this and, if needed, offer to continue the generation in a follow-up request.

Streaming Through SubToAPI

If you're building on Claude through SubToAPI, streaming works the same way described above — the /v1/messages endpoint accepts "stream": true and returns standard SSE events, so any existing streaming client code you have (curl, fetch, or an SDK) works without modification. You get an application API key (sub_live_...) instead of managing raw provider credentials, plus usage metadata and team seats on top. See the quickstart to get a key in a few minutes, or the streaming docs for the full event reference. Pricing starts at €9/month on the Solo plan, with team and scale tiers listed on the pricing page.

Questions

Does Claude streaming cost more than non-streaming requests? No. Pricing is based on input and output tokens, not on whether the response is streamed. Streaming only changes delivery, not billing.

Can I cancel a Claude stream partway through? Yes — closing the HTTP connection or aborting the fetch request stops the server from sending further events. Most SDKs expose an abort() or similar method for this.

Is streaming supported with tool use and function calling? Yes. Tool calls arrive as structured content blocks within the same SSE stream, alongside text deltas. See the tools docs for the exact event shapes when a model requests a tool call mid-stream.

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 →