FAQ: Conversational Triage API
If what you're looking for isn't answered here, check out our general FAQ.
Choosing an API
Engine API is stateless and evidence-driven: you send the full evidence list with every
/diagnosiscall, and you own the conversation flow — question rendering, answer mapping, when to stop. Conversational Triage API is stateful and conversation-driven: the patient writes free text, the server owns the transcript and the assessment, and each request carries only the new message.Choose Engine API when you want full control of the interview and are building your own UI logic on top of a medical reasoning engine. Choose Conversational Triage API when you want a natural-language triage experience without implementing the conversation layer yourself.
Platform API manages users, patients and surveys, and drives structured question flows through an interview. Conversational Triage API is a free-text conversation surface with its own base URL and its own conversation model. The two are complementary rather than alternatives — see also Platform API vs Engine API.
Use blocking
POST /turnsfor server-to-server integrations. Use HTTP NDJSON streaming for chat surfaces that render text as it is generated. Use the WebSocket transport for voice channels, interruption handling, and long-lived sessions with reconnect-and-resume.All three expose the same conversation model, so a conversation created on one can be read and continued on another.
Building a client
Almost always because the client builds message text from streaming deltas. Deltas carry text only — widgets never appear in them. Build message content from
content_partsonconversation.assistant_message.completed. See Messages and content parts.Conversation history is a union discriminated by
role: user rows exposecontentonly, assistant rows exposecontent_partsonly. Branch onrole.On the turn's terminal event —
conversation.turn.completed,conversation.turn.interrupted, orconversation.turn.failed. On HTTP NDJSON that is also when the stream closes; on blockingPOST /turnsit is when the response arrives. Gate your input control on that signal rather than on the last assistant message you received.On HTTP, a conversation processes one turn at a time, so a second send while a turn is in flight is rejected until that turn reaches a terminal state. Gate your send control on the in-flight request; if you do retry, reuse the same user-message
id— that call is idempotent and returns the existing turn. The WebSocket transport does not raise this error at all: a message sent mid-turn preempts the active turn instead.
Assessment and results
By design. On the blocking transport, refresh state with
GET /conversations/{conversation_id}/assessmentafter a successful turn — that is the canonical way to pick up the updated assessment. On the streaming transports the assessment arrives on every turn as aconversation.assessment.updatedevent.status(orconversation_statuson a turn) becomescompleted. Two situations produce that, andis_terminateddistinguishes them: a finished interview with arecommendation, or a permanent termination withrecommendationstillnull. Handle both — see Lifecycle and terminal states.Yes. Pass
age,sex,relationshipand anevidencelist inassessmentwhen creating the conversation. Seeded evidence is recorded withorigin: "seeded"so you can tell it apart from what the assistant collected. Ids must exist in the knowledge base, otherwise the create fails withunknown_evidence.That is the documented response —
200 OKwith three empty lists — when an explanation is unavailable: there is no recommendation yet, the condition is not in the recommendation set, or demographics are incomplete. Check for emptiness rather than for an error status.
Operations
Rate-limited responses carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Resetheaders, and successful responses on rate-limited routes carry them too — read your current limits from there rather than hard-coding a number. Exceeding the limit producesrate_limit_exceeded(429), withRetry-Afterwhen applicable.No. The server removes personally identifiable information from
content— and fromplayed_contenton interruptions — before the message reaches the engine, and read models return only the redacted text.Yes. Send a W3C Trace Context value in
user_message.traceparentwhen starting a turn, and keepconversation.idalongside your own identifiers.Call
GET /info— it returnssnomed_ct_versionas a SNOMED URI for the instance you are authenticated against. Record it alongside stored assessments so you know which terminology version produced them.