Streaming turns (NDJSON)
POST /conversations/{conversation_id}/turns/streamCreates one turn and streams its events as they happen. The request body is identical to POST /turns; the response is 200 OK with media type application/x-ndjson, one JSON event per line.
This is the canonical live HTTP transport — use it for chat surfaces that render text as it is generated, without the connection management a WebSocket requires.
curl -N "https://api.infermedica.com/api/ct/v2/conversations/68d9a84d-711c-4a55-a9dd-da6363e58e38/turns/stream" \
-X "POST" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "user_message": { "content": "I have a headache" } }'Events emitted
Each line is one event from the shared streaming events set. The subset this endpoint emits:
| Event | Meaning |
|---|---|
conversation.user_message.created | The message was accepted and a turn started. |
conversation.assistant_message.delta | Incremental text for an in-progress assistant message. |
conversation.assistant_message.ui_widget | Transient widget; only when ui_widgets_enabled is true. |
conversation.assistant_message.completed | Final form of one assistant message. |
conversation.assessment.updated | Full assessment snapshot — replace your local copy. |
conversation.handoff.requested | A handoff was recorded this turn. |
conversation.terminated | The conversation can no longer accept messages. |
conversation.completed | The conversation reached a terminal recommendation. |
conversation.turn.completed | Terminal event for a successful turn. |
conversation.turn.interrupted | Terminal event when a newer input superseded the turn. |
conversation.turn.failed | Terminal event when the turn failed. |
error | Recoverable protocol or processing error. |
The WebSocket-only events (conversation.created, conversation.resumed, conversation.settings.updated, ping) never appear here.
Example: a successful non-terminal turn
Widgets disabled:
{"type":"conversation.user_message.created","user_message":{"id":"68d9a84d-711c-4a55-a9dd-da6363e58e38","content":"I have a headache"}}
{"type":"conversation.assistant_message.delta","assistant_message":{"id":"6f605890-ff28-4b6d-a17f-d410f9faf558","turn_id":"a1b2c3d4-0000-0000-0000-000000000000","kind":"follow_up","can_interrupt":true,"delta":"How long have you had it?"}}
{"type":"conversation.assistant_message.completed","assistant_message":{"id":"6f605890-ff28-4b6d-a17f-d410f9faf558","turn_id":"a1b2c3d4-0000-0000-0000-000000000000","kind":"follow_up","can_interrupt":true,"content_parts":[{"type":"text","text":"How long have you had it?"}]}}
{"type":"conversation.assessment.updated","assessment":{"age":{"value":35,"unit":"year"},"sex":"female","relationship":"self","evidence":[{"id":"s_21","name":"Headache","common_name":"Headache","state":"present","origin":"asked","seriousness":null,"parent_id":null}],"unmatched_evidence":[],"conditions":[],"recommendation":null}}
{"type":"conversation.turn.completed","turn_id":"a1b2c3d4-0000-0000-0000-000000000000"}With ui_widgets_enabled set to true, a transient widget can arrive before the text completes:
{"type":"conversation.assistant_message.ui_widget","assistant_message":{"id":"6f605890-ff28-4b6d-a17f-d410f9faf558","turn_id":"a1b2c3d4-0000-0000-0000-000000000000","widget":{"type":"hints","hints":["Yes","No","Not sure"]}}}
{"type":"conversation.assistant_message.completed","assistant_message":{"id":"6f605890-ff28-4b6d-a17f-d410f9faf558","turn_id":"a1b2c3d4-0000-0000-0000-000000000000","kind":"intro","can_interrupt":true,"content_parts":[{"type":"text","text":"Do you have a fever?"},{"type":"ui_widget","widget":{"type":"hints","hints":["Yes","No","Not sure"]}}]}}When the stream ends
The stream closes once a terminal turn event — conversation.turn.completed, conversation.turn.interrupted, or conversation.turn.failed — has been emitted for the turn.
Client rules
- Build messages from
content_parts, never from accumulated deltas. Deltas carry text only; widgets never appear in them. A client that concatenates deltas silently drops them. - Replace the assessment, don't merge it.
conversation.assessment.updatedcarries the complete snapshot. - Treat widget events as progressive rendering only. They are transient and not replayed;
content_partson the completed event is the durable source of truth. - Expect exactly one terminal turn event per turn —
completed,interrupted, orfailed, mutually exclusive.
The canonical per-turn event order is documented under Successful turn order.