Stream chat responses
Consume Server-Sent Events safely and preserve forward compatibility.
The chat stream uses Server-Sent Events (SSE). Each frame contains an event name and a
camelCase JSON data payload. The payload's type matches the SSE event name.
event: message.content
data: {"type":"message.content","messageId":"ac_hist_...","content":"Hello"}
Event contract
| Event | Documented payload | Integration behavior |
|---|---|---|
message.start | role, messageId, messageTimestamp | Begin an assistant message. |
message.content | messageId, content | Append the text chunk to the matching message. |
message.stop | messageId | Finish that assistant message, not the consultation. |
conversation.offtopic | messageId | Record the off-topic classification and keep consuming the stream. |
conversation.end | messageId, reason | Mark the conversation as ended using the supplied reason. |
guardian | guardianId, message | If emitted, enter the safety behavior approved for your integration and pause conflicting routine flows. |
error | errorCode, message, optional retryAfterSeconds | Handle the stream error and respect the retry delay when present. |
skill_code.change_path | path | Record a conversation path change. |
skill_code.cta | skillCode, widget, optional text | Present the supplied next-step action. |
ai_consultation.complete | artifacts.longSummary, artifacts.shortSummary, artifacts.soapNote | Persist the artifact IDs and start artifact retrieval. |
Route safety events through an approved path
If guardian arrives, handle it independently from assistant message assembly and retain its
stable guardianId in your integration logs. Pause any routine local flow that conflicts with
the safety behavior approved for your integration. That approved behavior determines how the
supplied message is presented.
Browser implementation
The endpoint is a POST, so the browser's native EventSource interface is not sufficient.
Use fetch() and parse the response stream, or consume it from your backend and relay only
the events your frontend needs. Keep the API token on your backend.
const response = await fetch(`${baseUrl}/chats/${chatId}/stream/`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiToken}`,
'X-Doctronic-User-ID': userId,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
},
body: JSON.stringify({
userEvent: { type: 'user_message', userInput },
timezone: 'America/New_York',
}),
});
if (!response.ok || !response.body) {
throw new Error(`Stream failed: ${response.status}`);
}Stream state
Track state at three different levels:
message.stopfinishes one assistant message.conversation.endreports that the assistant ended the conversation.ai_consultation.completemakes stable consultation artifact IDs available. An artifact can still return202while generation is in progress.
The HTTP response ending only closes the current transport stream. It does not imply any of those application states.
Compatibility and retry rules
- Ignore event names you do not recognize.
- Do not infer safety, conversation end, or completion from assistant text.
- Key UI behavior from explicit event types and their documented fields.
- Preserve event order within a single HTTP stream.
- Associate message chunks by
messageId. - If an
errorincludesretryAfterSeconds, wait at least that long before retrying. - Do not automatically resend a user turn after partial output. A replay can duplicate work.