Streaming
Server-sent events on both endpoints, the always-on usage chunk, and the in-stream receipt comment.
Set "stream": true on either endpoint to receive server-sent events (SSE). The wire format is OpenAI's, byte-for-byte compatible with the official SDKs — plus one Draftworks addition (a receipt comment, covered below) that spec-compliant parsers ignore.
Chat Completions streams
Each event is a data: line carrying a chat.completion.chunk object; the stream terminates with data: [DONE].
data: {"id":"chatcmpl-a81f...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-a81f...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"The"},"finish_reason":null}]}
data: {"id":"chatcmpl-a81f...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-a81f...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":42,"completion_tokens":210,"total_tokens":252,"completion_tokens_details":{"reasoning_tokens":128}}}
: draftworks-receipt eyJ2IjoxLCJpZCI6InJjcHRfLi4uIn0.h4x1nQ...
data: [DONE]Usage is always included
Draftworks injects stream_options: { "include_usage": true } into every streamed request — metering requires exact token counts, and you get them too. The final chunk before [DONE] has an empty choices array and a populated usage object.
This is OpenAI-standard behavior (it is exactly what you get from OpenAI when you set include_usage yourself), so official SDKs handle it natively. If you wrote a hand-rolled parser that assumes every chunk contains choices[0], guard for the empty-choices chunk.
Responses API streams
The Responses API emits typed events, each with an event: name and data: payload:
event: response.created
data: {"type":"response.created","response":{"id":"resp_...","status":"in_progress",...}}
event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{"type":"reasoning",...}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_...","delta":"The CAP"}
event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_...","delta":" theorem"}
event: response.output_text.done
data: {"type":"response.output_text.done","item_id":"msg_...","text":"The CAP theorem states..."}
event: response.completed
data: {"type":"response.completed","response":{"id":"resp_...","status":"completed","usage":{...}}}
: draftworks-receipt eyJ2IjoxLCJpZCI6InJjcHRfLi4uIn0.k2p9vC...The response.completed event carries the full final response object, including usage.
The receipt comment
Because a streamed response cannot be signed until the last byte is generated, the receipt travels in-band: just before the stream ends, Draftworks injects one SSE comment line:
: draftworks-receipt <base64url-payload>.<base64url-signature>Lines beginning with : are comments in the SSE specification — every compliant parser, including the OpenAI SDKs, silently discards them. Your application code is unaffected unless you choose to read the receipt.
To capture it, watch the raw byte stream for lines with the : draftworks-receipt prefix and read the request id from the receipt payload — or ignore the comment entirely and fetch the receipt later from GET /v1/receipts/{id} using the request id from the response body (chatcmpl-… or resp_…). Streamed responses do not set the x-draftworks-receipt or x-draftworks-receipt-id headers; the receipt travels in-band as the SSE comment, since the response is signed only once the full stream is generated. Verification details, including how the stream hash is computed, are in verification.
Code
Python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://www.draftworks.dev/v1",
api_key=os.environ["DRAFTWORKS_API_KEY"],
)
stream = client.chat.completions.create(
model="gpt-5.5",
messages=[{"role": "user", "content": "Write a haiku about checksums."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage: # final chunk: empty choices, populated usage
print(f"\n\n[{chunk.usage.total_tokens} tokens]")TypeScript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://www.draftworks.dev/v1",
apiKey: process.env.DRAFTWORKS_API_KEY,
});
const stream = await client.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: "Write a haiku about checksums." }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) process.stdout.write(delta);
if (chunk.usage) {
console.log(`\n\n[${chunk.usage.total_tokens} tokens]`);
}
}Responses API streaming works the same way through the SDKs (client.responses.stream(...) in Python, client.responses.stream(...) / for await in TypeScript), yielding the typed events shown above.
Interrupted streams
If a stream drops mid-response, it is not resumable; retry the request. If your client aborts mid-stream, you are still billed for the full generation the upstream produced — metering runs independently of client delivery, and the upstream completes regardless of client disconnect. The receipt remains retrievable by its request id afterward. See errors for retry guidance.