Server-sent events, OpenAI shape.
Set "stream": true to receive tokens as they are generated. The event stream follows the OpenAI chunk format, ending with a usage-bearing final chunk and data: [DONE].
The request
curl https://api.webel.ai/v1/chat/completions \
-H "Authorization: Bearer $WEBEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"stream": true,
"messages": [{ "role": "user", "content": "Write a haiku about shipping." }]
}'
The response is Content-Type: text/event-stream. If the request itself is rejected (bad key, exhausted cap), you get a normal JSON error with the proper status code instead — the stream only opens after the turn is accepted.
The frames
Each frame is an SSE data: line carrying one JSON chunk. Content arrives as delta chunks:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Tokens"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" arrive"},"finish_reason":null}]}
The final chunk carries no content — it closes the choice and delivers the full usage block plus the webel block (conversation id, spend headroom):
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":18,"completion_tokens":24,"total_tokens":42,"cost_microusd":210000,"model_selected_by":"auto"},"webel":{"conversation":"…","turn":"…","flavor":"api"}}
data: [DONE]
- Usage arrives in the last data chunk, before
[DONE]— not in a separate trailing event. - Cost and the conversation id ride the same final chunk via
usage.cost_microusdandwebel.conversation. - Chunk
objectischat.completion.chunk; everything else matches the non-streaming contract.
Errors mid-stream
A failure after the stream has opened cannot change the status code, so it arrives as a final error frame followed by [DONE]:
data: {"error":{"message":"timed out waiting for the reply","type":"stream_error"}}
data: [DONE]
Treat any frame containing an error object as terminal: stop consuming, surface the message, and retry the request if appropriate.
Client notes
- OpenAI SDKs handle this automatically when
stream=True; unknown fields on chunks are ignored. - Keep the connection alive — events flush as they happen; there is no heartbeat padding frame.
- If your client disconnects mid-stream, the turn still completes server-side and its cost still accrues to the key.