Cookbook Recipe
Realtime & StreamingStream tokens to a terminal
The shortest path from an API key to tokens arriving in real-time on stdout: Server-Sent Events handling, final usage capture, cancellation, and clean error handling.
What you will build
When building CLI tools, bots, or conversational agents, waiting for the entire response to finish before rendering creates a sluggish user experience.
In this recipe, you will write a zero-dependency script that connects to /v1/chat/completions with Server-Sent Events (SSE). It writes each token to terminal output as soon as it arrives, intercepts the final usage chunk, and gracefully handles user cancellation via Ctrl+C.
Prerequisites
- Node.js 18+ or Python 3.10+.
- A DEVUP AI API key (
sk-devup-...) stored in your environment asDEVUP_API_KEY.
Complete runnable script
This script uses native fetch and the Streams API. It parses multi-byte chunks, decodes newline-delimited SSE lines, and supports clean cancellation:
import os
import sys
import json
import requests
api_key = os.environ.get("DEVUP_API_KEY")
if not api_key:
print("Please export DEVUP_API_KEY first.")
sys.exit(1)
url = "https://api.devupai.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": "deepseek-ai/DeepSeek-V3",
"messages": [
{"role": "user", "content": "Say 'Hello from DEVUP' and nothing else."}
],
"stream": True,
"stream_options": {"include_usage": True}
}
response = requests.post(url, headers=headers, json=payload, stream=True)
if not response.ok:
print(f"HTTP {response.status_code}: {response.text}")
sys.exit(1)
usage = None
for line in response.iter_lines():
if not line:
continue
line_str = line.decode("utf-8")
if not line_str.startswith("data: "):
continue
data = line_str[6:]
if data == "[DONE]":
break
chunk = json.loads(data)
choices = chunk.get("choices", [])
if choices:
delta = choices[0].get("delta", {}).get("content", "")
if delta:
sys.stdout.write(delta)
sys.stdout.flush()
if "usage" in chunk and chunk["usage"]:
usage = chunk["usage"]
if usage:
print("\n\n--- Usage & Billing ---")
print(f"Total tokens: {usage.get('total_tokens')}")
print(f"Estimated cost: {usage.get('estimated_cost')} DZD")Actual output
Below is the verbatim raw SSE frame sequence received over the wire for the request:
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[{"index":0,"delta":{"content":" from"},"finish_reason":null}]}
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[{"index":0,"delta":{"content":" DEVUP"},"finish_reason":null}]}
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-sample-id","object":"chat.completion.chunk","created":1789503772,"model":"deepseek-ai/DeepSeek-V3","choices":[],"usage":{"prompt_tokens":14,"total_tokens":19,"completion_tokens":5,"estimated_cost":0.00000893}}
data: [DONE]Cost transparency
Observed billing from this streaming call:
- Input: 14 prompt tokens.
- Output: 5 completion tokens.
- Observed estimated cost:
0.00000893 DZDtotal.
Setting stream_options: { include_usage: true } returns the final billing object right before [DONE] without making an extra API call.
What to do when it breaks
HTTP 401 — Invalid or deactivated API key
Occurs before any stream starts if the key is missing or revoked:
{
"error": {
"message": "The API key provided is invalid or has been deactivated.",
"type": "devup_error",
"code": "invalid_api_key"
}
}HTTP 404 — Model not found
Occurs if the model identifier does not match the active catalog:
{
"error": {
"message": "The requested model was not found or is unavailable.",
"type": "devup_error",
"code": "model_not_found"
}
}Fix: Check the catalog for valid slugs, such as deepseek-ai/DeepSeek-V3.
Honest limits
- SSE vs WebSockets: Server-Sent Events are unidirectional (server to client over standard HTTP/1.1 or HTTP/2). If you need bidirectional audio or mid-generation steerability, WebSockets are required.
- Browser rendering: In a web UI, appending tokens directly to React state on every frame can trigger expensive re-renders. Use requestAnimationFrame or buffer chunks for 16ms before updating UI components.