Documentation
Conversational Triage API
Basics
Conversation model

Conversation model

Both transports expose the same four concepts. Understanding how they nest is most of what you need to integrate.

Conversation                     ← server-owned, addressed by id
├── settings                     ← language, channel, widgets, handoff detectors
├── assessment                   ← evidence, conditions, recommendation
└── messages[]                   ← persisted history (user + assistant rows)

Turn                             ← one accepted user message + the assistant output it produced
├── user_message
└── assistant_messages[]
    └── content_parts[]          ← text | ui_widget

Conversation

A conversation is the unit of state. You create one, then send user messages to it; the server keeps the transcript and the assessment, so a request never resends earlier evidence.

GET /conversations/{conversation_id} returns the current snapshot at any time — useful after a reconnect, or when a second device joins the same session.

Turn

A turn is one accepted user input plus the assistant output it produced. It is also the unit of concurrency, and the two transports resolve a second input differently:

TransportA user message sent while a turn is active
HTTP (blocking and NDJSON)Rejected with turn_already_active (409). The active turn continues.
WebSocketAccepted. It preempts the active turn, which ends with conversation.turn.interrupted, and starts a new one. turn_already_active never occurs on this transport.

This is a property of the transport, not of the message: no field on UserMessageInput selects between the two behaviours.

Every turn ends in exactly one of three terminal states:

statusMeaning
completedThe turn finished normally.
interruptedA newer accepted input superseded this turn.
failedProcessing of the turn failed.

Messages

Assistant output is an ordered list of content parts, not a single string. A part is either plain text or a ui_widget. There is no top-level content field on assistant messages, and no separate ui_widgets array.

Conversation history and turn/stream payloads use different types for the same messages — history rows are discriminated by role, turn and stream payloads add fields such as can_interrupt and deltas. See Messages and content parts.

Assessment

The assessment is the structured medical state behind the conversation: patient demographics, collected evidence, the differential conditions, and — once the interview concludes — the triage recommendation. It is a conversation-level object, not a per-turn one.

How you obtain it depends on the transport:

  • Blocking HTTP — the turn response omits the assessment; call GET /conversations/{conversation_id}/assessment after each turn.
  • Streaming and WebSocket — a conversation.assessment.updated event carries the full snapshot; replace your local copy with it rather than merging.

See Assessment and results.

Lifecycle and terminal states

Conversation.status (conversation_status on a Turn) tells you whether the conversation still accepts user messages:

statusMeaning
in_progressThe conversation accepts further user messages.
completedThe conversation rejects further user messages.

Two very different situations produce completed, and is_terminated (conversation_terminated on a Turn) distinguishes them:

statusis_terminatedWhat happenedRejection code
completedfalseThe interview finished and produced a recommendation.conversation_completed
completedtrueThe conversation was permanently terminated — for example an underage patient or a confirmed change of age or sex.conversation_terminated

In both cases the only way forward is a new conversation. The explanation for the user arrives in that turn's assistant messages — render it instead of mapping reasons client-side.

Was this page helpful?