Streaming

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_microusd and webel.conversation.
  • Chunk object is chat.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.