Streaming SSE
On this page
Real-time responses
In normal mode, you wait for the model to finish generating the complete response before receiving it. With SSE streaming (Server-Sent Events), tokens arrive as they are generated. This is essential for conversational interfaces where users want to see the response appear progressively.
Enable streaming
Add "stream": true to your request:
curl https://api.x.ai/v1/responses \
-H "Authorization: Bearer $XAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-4.20-0309-reasoning",
"input": "Write a poem about programming.",
"stream": true
}'
The response arrives as an SSE stream:
data: {"type": "response.created", "response": {...}}
data: {"type": "response.output_item.added", ...}
data: {"type": "response.content_part.added", ...}
data: {"type": "response.output_text.delta", "delta": "La "}
data: {"type": "response.output_text.delta", "delta": "programmation "}
data: {"type": "response.output_text.delta", "delta": "est "}
...
data: {"type": "response.completed", "response": {...}}
Event types
| Event | Description |
|---|---|
response.created | Generation starts |
response.output_item.added | New item in output |
response.content_part.added | New content part |
response.output_text.delta | Text fragment (most frequent) |
response.output_text.done | Complete text of an item |
response.completed | Response finalized with usage |
response.failed | Error during generation |
Streaming in Python
stream = client.responses.create(
model="grok-4.20-0309-reasoning",
input="Explain how a compiler works.",
stream=True
)
full_text = ""
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
full_text += event.delta
elif event.type == "response.completed":
usage = event.response.usage
print(f"\n\nTokens: {usage.total_tokens}")
print(f"Cost: {usage.cost_in_nano_usd / 1e9:.6f} USD")
flush=True forces immediate display of each fragment.
Streaming with tools
When the model uses tools in streaming mode, you receive additional events:
data: {"type": "response.output_item.added", "item": {"type": "web_search_call"}}
data: {"type": "response.web_search_call.in_progress", ...}
data: {"type": "response.web_search_call.completed", ...}
data: {"type": "response.output_text.delta", "delta": "Selon les résultats..."}
For client function calls, you receive arguments progressively:
data: {"type": "response.function_call.arguments.delta", "delta": "{\"city\""}
data: {"type": "response.function_call.arguments.delta", "delta": ": \"Paris\"}"}
data: {"type": "response.function_call.arguments.done", ...}
Frontend integration
For a web application, send the SSE stream to the browser via a server endpoint:
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: userInput })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
outputElement.textContent += text;
}
Streaming and billing
Streaming doesn’t change billing. The cost is identical whether it’s enabled or not—only the delivery mode differs. The complete usage object is available in the response.completed event.
Key takeaways
stream: trueenables SSE streaming—tokens arrive as they are generated- The
response.output_text.deltaevent contains each text fragment - The
response.completedevent contains the final response with usage metadata - Streaming works with tools (web_search, functions)
- Cost is identical with or without streaming