MemberPress AI Academy memberpress.ai

First Call · Your First API Call

Streaming vs. blocking

Video16 min

Blocking and streaming use the same endpoint and the same request body. One field changes. The experience for a human reading the output changes completely.

Blocking mode — the full response arrives at once

Everything you have written so far uses blocking mode. The connection stays open until the model finishes, then the full JSON response arrives. Your code reads content[0].text and moves on.

This is the right choice when the response feeds downstream work: a database write, another API call, a test assertion. You cannot do any of that work on partial text.

Streaming mode — tokens arrive as they are generated

Add "stream": true to the request body. The endpoint switches from returning a single JSON object to returning a stream of server-sent events (SSE).

curl 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-opus-4-5",
    "max_tokens": 512,
    "stream": true,
    "messages": [{"role": "user", "content": "Count to ten slowly, one word per line."}]
  }'

The terminal will print raw SSE lines as they arrive. You will see events like event: content_block_delta with delta.text containing the next few characters, followed eventually by event: message_stop.

Handling streaming in Python

# scripts/stream.py
import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-opus-4-5",
    max_tokens=512,
    messages=[{"role": "user", "content": "Write a two-sentence summary of HTTP."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    print()  # newline after stream completes
    final = stream.get_final_message()
    print(f"\ninput_tokens={final.usage.input_tokens} output_tokens={final.usage.output_tokens}")

The text_stream iterator yields each partial text delta. get_final_message() returns the complete message object — including usage — after message_stop. You cannot access usage mid-stream because token counts are not finalized until generation ends.

Handling streaming in Node.js

Many web apps handle Claude responses in a JavaScript backend. The pattern is the same, just spelled differently:

// scripts/stream.mjs
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const stream = await client.messages.stream({
  model: "claude-opus-4-5",
  max_tokens: 512,
  messages: [{ role: "user", content: "Write a two-sentence summary of HTTP." }],
});

for await (const chunk of stream) {
  if (
    chunk.type === "content_block_delta" &&
    chunk.delta.type === "text_delta"
  ) {
    process.stdout.write(chunk.delta.text);
  }
}

const final = await stream.finalMessage();
console.log(`\ninput_tokens=${final.usage.input_tokens}`);

Run with node scripts/stream.mjs after npm install @anthropic-ai/sdk. The chunk shape mirrors the Python SDK: content_block_delta events carry the text, message_stop terminates the stream.

When to choose each mode

Streaming makes a ten-second response feel interactive because the first tokens arrive in under a second. For chat interfaces, search results, and any output a human reads live, streaming is the better choice.

Blocking is simpler to reason about. The response is either there or it is not. Use it for batch processing, pipelines, and any situation where the full response is needed before the next step runs.

The message_stop event matters

In a raw SSE implementation (not using the SDK's messages.stream helper), you must detect event: message_stop to know the stream is done. Stopping early — closing the connection before this event — means you silently miss the end of the response. The SDK handles this for you, but if you are implementing streaming in a language without an official SDK, this is the failure mode to watch for.

If you get stuck

  • Terminal prints nothing and hangs: the stream: true flag may not have been parsed correctly. Check that your JSON is valid and that the field is a boolean, not the string "true".
  • Stream ends mid-sentence: max_tokens was hit. Check stop_reason in the final message event — it will say max_tokens instead of end_turn.
  • usage is all zeros: you are reading usage from a partial event rather than from the final message. Call get_final_message() (Python) or finalMessage() (Node) after the stream completes.
  • SSE lines look garbled: each event is prefixed with data: — parse the JSON after that prefix, not the whole line.
  • Node: for await loop exits before the stream finishes: confirm you are using stream (the async iterable) not stream.on('data', ...). The SDK exposes both; the for await form is safer.

Cross-reference

l7 (Wrap a call in a small CLI) shows how to wire streaming output to stdout cleanly via the --stream flag. l4 (Errors, retries, and timeouts) covers what happens when a streaming connection drops mid-response.

Deliverable

Run the same prompt in both blocking and streaming mode. Print the elapsed wall time for each. Confirm that the accumulated streaming text matches the blocking response text character-for-character.

Try these prompts

  • Write the Python code to stream a Claude response and print each token to stdout as it arrives, then print the final token count.
  • When would you choose blocking mode over streaming? Name two concrete scenarios where blocking is the right choice.
  • Write the Node.js for-await loop that reads content_block_delta events from a streaming Claude response.