Wire format
Streaming uses server-sent events exactly like OpenAI Chat Completions: a series of data: {...} lines with chat.completion.chunk objects, ending with data: [DONE].
choices[0].delta.reasoning_content— the pipeline's working, in full: the brief, every draft as it lands (headed━━ Draft A · verify lens · …), and the audit. Billed as reasoning tokens (usage.completion_tokens_details.reasoning_tokens).choices[0].delta.content— the answer itself.- The final chunk carries
usage(withcostin USD) and atempestobject.
Passing stream_options: {"include_usage": true} is harmless; the final usage chunk is sent either way.
curl
curl -N https://api.failure.fail/v1/chat/completions \
-H "Authorization: Bearer $FAILURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "failure-tempest-1",
"stream": true,
"messages": [{"role": "user", "content": "Why does TCP need a three-way handshake?"}]
}'
Python
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.failure.fail/v1", api_key=os.environ["FAILURE_API_KEY"])
stream = client.chat.completions.create(
model="failure-tempest-1",
messages=[{"role": "user", "content": "Why does TCP need a three-way handshake?"}],
stream=True,
stream_options={"include_usage": True},
)
answer, working = [], []
final = None
for chunk in stream:
if chunk.choices:
delta = chunk.choices[0].delta
r = getattr(delta, "reasoning_content", None) # extra field, not in the SDK types
if r:
working.append(r)
if delta.content:
answer.append(delta.content)
print(delta.content, end="", flush=True)
if chunk.usage:
final = chunk
print()
u = final.usage
print("prompt", u.prompt_tokens, "completion", u.completion_tokens)
print("cached", u.prompt_tokens_details.cached_tokens if u.prompt_tokens_details else 0)
extra = final.model_extra or {}
print("cost", getattr(u, "cost", None), "tempest", extra.get("tempest"))
TypeScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.failure.fail/v1", apiKey: process.env.FAILURE_API_KEY });
const stream = await client.chat.completions.create({
model: "failure-tempest-1",
messages: [{ role: "user", content: "Why does TCP need a three-way handshake?" }],
stream: true,
stream_options: { include_usage: true },
});
let answer = "";
for await (const chunk of stream) {
const delta: any = chunk.choices[0]?.delta ?? {};
if (delta.reasoning_content) process.stderr.write(delta.reasoning_content);
if (delta.content) {
answer += delta.content;
process.stdout.write(delta.content);
}
if (chunk.usage) {
const u: any = chunk.usage;
console.log("\nusage", u.prompt_tokens, u.completion_tokens, u.prompt_tokens_details?.cached_tokens, "cost", u.cost);
console.log("tempest", (chunk as any).tempest);
}
}
The final chunk
{
"id": "chatcmpl-...",
"object": "chat.completion.chunk",
"model": "failure-tempest-1",
"choices": [],
"usage": {
"prompt_tokens": 18,
"completion_tokens": 412,
"prompt_tokens_details": { "cached_tokens": 0 },
"completion_tokens_details": { "reasoning_tokens": 318 },
"cost": 0.01245
},
"tempest": {
"task": "...",
"difficulty": "...",
"stage": "...",
"cache": "...",
"billed_usd": 0.01245
}
}
Illustrative values. The fields are the contract; the numbers are not measurements.
Showing the working
A fused answer on a hard prompt waits for drafts. Show the reasoning_content stream in a collapsible panel so users can see progress, and render content as the answer. Do not feed reasoning_content back into later turns — send only the answer as the assistant message.
Errors mid-stream
Errors before the stream opens arrive as normal HTTP errors (see errors). If the connection drops mid-stream, treat the partial answer as incomplete and retry the whole request.