Streaming Responses¶
For a responsive, chat-like experience, stream the model's reply as it's generated instead of waiting for the whole thing. Add "stream": true to a chat completion request and the response is delivered as Server-Sent Events (SSE).
POST https://api.blaick.ai/api/v1/chat/completions
Content-Type: application/json
Accept: text/event-stream
{ "model_id": "...", "messages": [...], "stream": true }
The event stream¶
Each event is a line beginning with data: followed by a JSON object. Every object has a type field. There are four types:
type |
When | Payload |
|---|---|---|
delta |
Repeatedly, as text is generated | content — the next chunk of text to append |
usage |
Once, near the end | input_tokens, output_tokens, total_tokens, cost_tokens |
done |
Once, at the very end | auto_routed, routing_reason, routing_task_type |
error |
If something goes wrong mid-stream | message |
A complete stream looks like this:
data: {"type": "delta", "content": "The"}
data: {"type": "delta", "content": " capital"}
data: {"type": "delta", "content": " of France is Paris."}
data: {"type": "usage", "input_tokens": 18, "output_tokens": 8, "total_tokens": 26, "cost_tokens": 52}
data: {"type": "done", "auto_routed": false, "routing_reason": null, "routing_task_type": null}
To assemble the full reply, concatenate the content of every delta event in order. When you receive usage, you have the final token cost. When you receive done, the response is complete.
Handle error events
An error can arrive mid-stream (for example, an upstream provider dropping the connection) as {"type": "error", "message": "..."}. Treat it as a terminal event and surface the message.
Consuming the stream¶
Use -N to disable buffering so events print as they arrive:
import json, os, requests
BASE = "https://api.blaick.ai/api/v1"
headers = {"Authorization": f"Bearer {os.environ['BLAICK_API_KEY']}"}
with requests.post(
f"{BASE}/chat/completions",
headers=headers,
json={
"model_id": "claude-sonnet-4-20250514",
"messages": [{"role": "user", "content": "Write a haiku about the sea."}],
"stream": True,
},
stream=True,
) as resp:
resp.raise_for_status()
full = ""
for line in resp.iter_lines(decode_unicode=True):
if not line or not line.startswith("data:"):
continue
event = json.loads(line[len("data:"):].strip())
if event["type"] == "delta":
full += event["content"]
print(event["content"], end="", flush=True)
elif event["type"] == "usage":
print(f"\n\nCost: {event['cost_tokens']} tokens")
elif event["type"] == "done":
break
elif event["type"] == "error":
raise RuntimeError(event["message"])
const BASE = "https://api.blaick.ai/api/v1";
const resp = await fetch(`${BASE}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLAICK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model_id: "claude-sonnet-4-20250514",
messages: [{ role: "user", content: "Write a haiku about the sea." }],
stream: true,
}),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let full = "";
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 the last, possibly-incomplete line
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const event = JSON.parse(line.slice(5).trim());
if (event.type === "delta") {
full += event.content;
process.stdout.write(event.content);
} else if (event.type === "usage") {
console.log(`\n\nCost: ${event.cost_tokens} tokens`);
} else if (event.type === "error") {
throw new Error(event.message);
}
}
}
Buffer across chunks
Network chunks don't align to event boundaries — a single delta line may be split across two reads. Buffer incoming bytes and only parse complete lines (as the JavaScript example does by holding back the last split segment).
WebSocket streaming¶
For bidirectional, real-time chat, connect to the WebSocket endpoint instead:
Authenticate by passing your token as a query parameter, then send and receive JSON messages:
const ws = new WebSocket(
`wss://api.blaick.ai/api/v1/chat/ws/${conversationId}?token=${apiKey}`
);
ws.onopen = () => {
ws.send(JSON.stringify({
type: "message",
content: "Hello!",
model_id: "claude-sonnet-4-20250514",
}));
};
ws.onmessage = (evt) => {
const msg = JSON.parse(evt.data);
if (msg.type === "delta") process.stdout.write(msg.content);
else if (msg.type === "usage") console.log("\nCost:", msg.cost_tokens);
else if (msg.type === "done") console.log("\n[complete]");
else if (msg.type === "error") console.error(msg.message);
};
The server sends the same delta / usage / done / error message types. WebSocket is best when you want a persistent, low-latency channel for an ongoing conversation; plain SSE over POST is simpler for one-off completions.