DEVUP Docs
Back to Dashboard

Cookbook Recipe

Realtime & Streaming

Stream 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.

POSThttps://api.devupai.com/v1/chat/completions

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 as DEVUP_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:

Raw SSE Stream Transcript
text
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 DZD total.

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:

json
{
  "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:

json
{
  "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.

Where to go next