Documentation
Conversational Triage API
Build your solution
Turns

Turns

A turn is one accepted user message plus the assistant output it produced. POST /turns is the blocking variant: it returns once the turn is finished.

POST /conversations/{conversation_id}/turns

Sending a message

cURL
curl "https://api.infermedica.com/api/ct/v2/conversations/68d9a84d-711c-4a55-a9dd-da6363e58e38/turns" \
  -X "POST" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_message": {
      "id": "68d9a84d-711c-4a55-a9dd-da6363e58e38",
      "content": "I have a headache"
    }
  }'

user_message is a UserMessageInput:

FieldTypeRequiredDescription
idUUIDnoClient-generated id for local correlation. The server assigns one when omitted.
contentstringyesThe user's text. Non-empty, and at most max_message_length Unicode code points, measured before PII redaction.
is_interruptedbooleannoThe user cut off an in-flight assistant message — typical in voice channels. Default false.
played_contentstringnoThe assistant text actually played before the interruption. Omit when not interrupting.
traceparentstringnoW3C Trace Context value linking this turn to your own trace.
ℹ️

Before the message reaches the engine, the server removes personally identifiable information from content (and from played_content). Read models return only the redacted text, so treat content in responses as the only safe form of the message.

The response

Response: 201 Created, body is a Turn.

JSON
{
  "id": "a1b2c3d4-0000-0000-0000-000000000000",
  "status": "completed",
  "conversation_status": "in_progress",
  "user_message": {
    "id": "68d9a84d-711c-4a55-a9dd-da6363e58e38",
    "content": "I have a headache"
  },
  "assistant_messages": [
    {
      "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?" }
      ]
    }
  ],
  "handoff": null,
  "conversation_terminated": false
}
FieldTypeDescription
idUUIDThe turn created by this request.
statuscompleted, interrupted, or failedTurn outcome.
conversation_statusin_progress or completedWhether the conversation still accepts messages.
user_messageUserMessageThe accepted input, post-redaction.
assistant_messagesAssistantMessage[]Assistant output, in order.
handoffHandoff or nullSet when a handoff was requested during the turn.
conversation_terminatedbooleantrue when the turn left the conversation permanently unable to continue.

Two things the response deliberately does not contain:

  • The assessment. Refresh it with GET /conversations/{conversation_id}/assessment after a successful turn. That call is the canonical way to pick up the updated state on this transport.
  • Transient widget events. Blocking turns return the final content_parts on each assistant message; the progressive conversation.assistant_message.ui_widget event exists only on the streaming transports.

One turn at a time

On this transport a conversation processes one turn at a time: sending a message while another turn is active fails with turn_already_active (409). Gate your input control on the in-flight request. Retrying is safe when you reuse the same user-message id — that call is idempotent and returns the existing turn rather than a second one — but a retry under a new id fails again until the active turn reaches a terminal state.

The WebSocket transport behaves differently: a message sent mid-turn preempts the active turn instead of being rejected. See Realtime.

Once the conversation stops accepting messages, further calls fail with conversation_completed or conversation_terminated (both 409). See Lifecycle and terminal states.

Reading assistant output

Assistant messages have no top-level content. Build the rendered message from content_parts, in order:

const text = message.content_parts
  .filter((part) => part.type === 'text')
  .map((part) => part.text)
  .join('')

kind tells you the message's role within the turn — acknowledgement, bridge, filler, intro, or follow_up — which is useful for styling. can_interrupt tells a voice client whether the segment may be cut off, and is derived from kind alone. See Messages and content parts.

Was this page helpful?