← Blog

Claude API Streaming: Token-by-Token Output Guide

2026-10-31 · 4 min read · SubToAPI Team

If you want Claude to return text progressively — word by word, the way ChatGPT and Claude.ai render responses — you need to enable streaming on your API request instead of waiting for the full completion. Streaming sends the response as a sequence of server-sent events (SSE), where each event contains a small chunk of generated text (a token or a few tokens), so your UI can display output as it's produced instead of showing a blank screen until generation finishes.

This matters for two reasons: perceived latency and UX. A 500-token response might take 4-8 seconds to generate in full. Without streaming, the user stares at a spinner for the entire duration. With streaming, the first tokens appear within a few hundred milliseconds and the rest trickles in, which feels dramatically faster even though total generation time is identical. Below is exactly how token-by-token streaming works, how to implement it, and the pitfalls that trip people up.

How streaming actually works

When you set "stream": true in your request, the API keeps the HTTP connection open and sends a series of SSE events instead of one JSON blob. The event stream typically includes:

The piece you care about for "token by token" output is content_block_delta. Each event in this type carries a small fragment of text under delta.text. Concatenating all of these in order reconstructs the full response.

Streaming with curl

A raw streaming request against an Anthropic-compatible API looks like this:

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

With stream: true, the terminal prints raw SSE lines as they arrive — you'll see event: content_block_delta lines interleaved with data: {...} payloads, appearing progressively instead of all at once.

Streaming with JavaScript

In a browser or Node environment, you consume the stream by reading the response body incrementally rather than calling .json(). Here's a minimal fetch-based implementation:

async function streamCompletion(prompt) {
  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: 512,
      stream: true,
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let fullText = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split("\n").filter((l) => l.startsWith("data:"));

    for (const line of lines) {
      const json = line.replace("data: ", "").trim();
      if (json === "[DONE]" || !json) continue;

      try {
        const event = JSON.parse(json);
        if (event.type === "content_block_delta") {
          const text = event.delta?.text || "";
          fullText += text;
          process.stdout.write(text); // render token by token
        }
      } catch {
        // partial chunk, ignore and wait for more data
      }
    }
  }

  return fullText;
}

The key detail: SSE chunks don't always align with event boundaries. A single read() call might return half an event, or multiple events at once. Always buffer incomplete lines and parse defensively — wrapping JSON.parse in a try/catch (or maintaining a buffer until you see a full data: line) prevents crashes on partial payloads.

Rendering tokens in a UI

For a chat interface, the pattern is usually:

  1. Append a new empty message bubble when message_start fires.
  2. On each content_block_delta, append delta.text to that bubble's content state.
  3. On message_stop, mark the message as complete (stop showing a typing cursor, re-enable the input box).

If you're using React, this means updating state on every delta event, which can be dozens of times per second for a fast response. Debouncing re-renders (batching deltas every 30-50ms) keeps the UI smooth without dropping any text — you still display everything, just not on every single token.

Common streaming pitfalls

SubToAPI exposes the same SSE streaming interface over your existing Claude access, so any client built against the Anthropic streaming format works without modification — see /docs/streaming for the full event reference and /docs/quickstart to get an API key in under five minutes.

Questions

Does streaming cost more than a non-streaming request? No. Token usage and pricing are identical — streaming only changes how the response is delivered (incrementally vs. all at once), not how many tokens are generated or billed.

Can I stream and also use tool calling at the same time? Yes. Streamed tool-use responses send input_json_delta events that build up the tool call's arguments incrementally, in addition to content_block_delta for any plain text output.

What happens if the connection drops mid-stream? You lose whatever hasn't arrived yet and need to retry the request from scratch — streaming doesn't support resuming a partial response, so your client should treat a dropped connection as a failed request.

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 →