← Blog

Claude API Server-Sent Events Example

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

When you call the Claude API with "stream": true, the response isn't a single JSON blob — it's a sequence of server-sent events (SSE), each one a small JSON payload describing a piece of the model's output as it's generated. This article shows exactly what those events look like, how to parse them with curl and JavaScript, and what to watch out for when building a real streaming client.

If you just want to see raw output fast, curl with -N (no buffering) against the Claude API streaming endpoint will print each SSE frame as it arrives. If you want to build a UI that updates token-by-token, you need to parse the event: and data: lines and reconstruct the message incrementally. Both are covered below.

What SSE Looks Like on the Wire

Server-sent events are plain text over an HTTP connection. Each event is a block separated by a blank line, with a data: field containing JSON and often an event: field naming the event type:

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

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: message_stop
data: {"type":"message_stop"}

The client's job is to read these events in order, accumulate the text_delta chunks, and know when the stream is finished.

A Minimal curl Example

This is a generic streaming call to a Claude-compatible messages endpoint:

curl -N https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 200,
    "stream": true,
    "messages": [{"role": "user", "content": "Write one sentence about the sea."}]
  }'

The -N flag is important — without it, curl may buffer output and you won't see events arrive incrementally. You'll see the raw event:/data: frames scroll by as the model generates text.

Parsing SSE in JavaScript

EventSource doesn't support custom headers or POST bodies, so for authenticated APIs you typically read the stream manually from fetch using a ReadableStream reader and split on newlines:

async function streamMessage(payload) {
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({ ...payload, stream: true }),
  });

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

  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 json = line.replace("data: ", "").trim();
      if (!json) continue;

      const event = JSON.parse(json);
      if (event.type === "content_block_delta" && event.delta?.text) {
        fullText += event.delta.text;
        process.stdout.write(event.delta.text); // print as it arrives
      }
    }
  }

  return fullText;
}

This pattern — buffer bytes, split on newlines, parse data: lines as JSON — works for any SSE-based LLM API, not just Claude.

Event Types You'll Encounter

A typical streamed Claude response emits these event types in order:

Handling each type correctly matters more than it looks. If your parser only checks for content_block_delta and ignores message_delta, you'll never capture token usage or the final stop_reason, which you often need for billing or deciding whether the model stopped because of max_tokens versus finishing naturally.

Handling Tool Use and Errors Mid-Stream

When Claude calls a tool during a streamed response, the arguments arrive as partial JSON across multiple content_block_delta events with type: "input_json_delta". You need to concatenate the partial_json strings and parse the full JSON only once content_block_stop fires for that block — parsing mid-stream will throw on incomplete JSON.

Errors can also arrive as SSE events rather than HTTP error codes, since the connection is already open and streaming. Always check for event.type === "error" inside your read loop, not just the initial response status, or your client will silently hang on a failed stream.

Streaming Through SubToAPI

If you're already past the "raw SSE parsing" stage and want streaming without managing provider keys, rate limits, or reconnect logic yourself, SubToAPI exposes the same SSE streaming model over a stable HTTPS endpoint:

curl -N https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 200,
    "stream": true,
    "messages": [{"role": "user", "content": "Write one sentence about the sea."}]
  }'

The event format is identical to what's described above, so any parser you write against it works unchanged. SubToAPI adds application API keys (sub_live_...), per-key usage metadata, and team seat management on top — useful if multiple services or team members need streaming access without sharing one provider key. See the streaming docs and quickstart for setup details, or check pricing if you're evaluating it for a team.

Questions

Does the Claude API support true token-by-token streaming over SSE? Yes. Setting "stream": true returns a text/event-stream response with content_block_delta events carrying small text chunks as the model generates them, rather than one blocking JSON response.

Can I use the browser's built-in EventSource for Claude API streams? Not directly — EventSource only supports GET requests and can't send custom headers like your API key. Use fetch with a ReadableStream reader and parse data: lines manually, as shown above.

What's the difference between content_block_delta and message_delta events? content_block_delta carries incremental content (text or tool argument JSON). message_delta carries message-level metadata updates like stop_reason and token usage, and typically arrives near the end of the 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 →