Docs

Conversations

Run a consultation end to end. Create a chat, stream each patient turn, pass context, and restore the transcript.

A consultation runs in one chat. Your backend creates the chat for a user, sends each patient turn to it, and reads the assistant's reply as a stream of typed events.

curl --fail-with-body --request POST "$DOCTRONIC_API_URL/chats/" \
  --header "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
  --header "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
  --header "Idempotency-Key: $(uuidgen)" \
  --header "Content-Type: application/json" \
  --data '{
    "timezone": "America/New_York",
    "conversationPromptContext": "The patient entered from a sore-throat intake flow and reported symptoms beginning yesterday."
  }'
import { randomUUID } from 'node:crypto';

const baseUrl = process.env.DOCTRONIC_API_URL ?? 'https://staging-partners.doctronic.ai/api/v1';

const response = await fetch(`${baseUrl}/chats/`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.DOCTRONIC_API_TOKEN}`,
    'X-Doctronic-User-ID': process.env.DOCTRONIC_USER_ID ?? '',
    'Idempotency-Key': randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    timezone: 'America/New_York',
    conversationPromptContext:
      'The patient entered from a sore-throat intake flow and reported symptoms beginning yesterday.',
  }),
});
if (!response.ok) throw new Error(`Doctronic ${response.status}: ${await response.text()}`);
const { data: chat } = await response.json();
// Save chat.id with the consultation record in your system.
201 Created
{
  "code": "success",
  "message": "Request successful",
  "data": {
    "id": "chat_01K6JGGM2G2DB4Y9KB9H9MJJ6Q",
    "createdAt": "2026-08-31T14:31:00Z"
  }
}

How a consultation works

User (mem_…)                    one per patient
  └── Chat (chat_…)             one per consultation
        ├── Turn                POST /chats/{chatId}/stream/ → SSE events
        ├── Turn                …repeat until the consultation completes
        └── ai_consultation.complete
              └── Consultation documents (art_…)
  1. Resolve the patient's user ID from your mapping.
  2. Create a chat and save data.id.
  3. Send each patient message as a turn and read the events it returns.
  4. When ai_consultation.complete arrives, store the artifact IDs and retrieve the consultation documents.

Create a chat

POST /chats/ takes two optional fields:

FieldDefaultNotes
timezoneUTCAn IANA timezone, such as America/New_York. An invalid value returns 400.
conversationPromptContextNoneContext for the whole consultation. See Pass context.

Content that fails input validation returns 400 error.partner.validation. The request accepts an Idempotency-Key.

GET /chats/{chatId}/ returns one chat. A chat that belongs to another user or organization returns 404 error.partner.chat_not_found, the same as a chat that doesn't exist.

Send a turn

Each patient message is one turn. Send it to POST /chats/{chatId}/stream/ and keep the response open.

curl --fail-with-body --no-buffer --request POST \
  "$DOCTRONIC_API_URL/chats/$DOCTRONIC_CHAT_ID/stream/" \
  --header "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
  --header "X-Doctronic-User-ID: $DOCTRONIC_USER_ID" \
  --header "Content-Type: application/json" \
  --header "Accept: text/event-stream" \
  --data '{
    "userEvent": {
      "type": "user_message",
      "userInput": "I have had a sore throat and fever since yesterday."
    },
    "timezone": "America/New_York"
  }'

Parse the stream has the same request with fetch, httpx, and both SDKs, and handles every event.

FieldRequiredNotes
userEvent.typeNoAlways user_message.
userEvent.userInputYesThe patient's message.
userEvent.messagePromptContextNoContext for this turn only. See Pass context.
timezoneNoAn IANA timezone. Defaults to UTC; an invalid value returns 400.

The SDKs build userEvent for you: pass userInput and messagePromptContext, or user_input and message_prompt_context in Python.

The response is text/event-stream, not the JSON envelope. It stays open for the assistant's whole turn and closes when the turn ends. Wait for it to close before you send the next turn.

The stream takes no idempotency key, so do not resend a turn automatically after an interrupted stream: the first attempt may already have produced a reply. See stream errors.

Handle events

The stream mixes message text with typed control events. Build each message from message.* events keyed by messageId, run your safety behavior on guardian, present next steps from conversation.end and skill_code.cta, and collect document IDs from ai_consultation.complete. Streaming has every event and its payload, production parsers, and the compatibility rules.

Pass context

Give Doctronic context at two levels:

FieldWhereApplies toLimit
conversationPromptContextPOST /chats/ bodyThe whole consultation50,000 characters
messagePromptContextuserEvent in the stream bodyOne turn50,000 characters

Both fields are clinical input. Use them for facts and guidance that help the consultation: why the patient entered the workflow, answers from an intake step before the chat, a short handoff from another approved clinical workflow, constraints the patient chose (such as language or location), and your approved voice and tone.

Leave out whole application state, unrestricted chart exports, routing commands, credentials, and anything the current workflow isn't authorized to use.

Put tone guidance in conversationPromptContext so it holds for the whole consultation:

Chat-level tone
{
  "timezone": "America/New_York",
  "conversationPromptContext": "Use a calm, reassuring, concise tone. Explain medical terms in plain language and ask one question at a time."
}

Use messagePromptContext for something true of one turn only:

Turn-level context
{
  "userEvent": {
    "type": "user_message",
    "userInput": "My fever is 101.5 F.",
    "messagePromptContext": "Temperature was entered in the intake form immediately before this turn."
  },
  "timezone": "America/New_York"
}

Context shapes phrasing and level of detail. The clinical workflow, safety behavior, and event contract stay the same whatever you send, so context can't skip required questions or suppress escalation.

End states

Four signals look like endings. Each one means something different:

SignalWhat it meansWhat to do
message.stopOne message is complete.Finalize that message. The turn and the consultation continue.
HTTP stream closesThe assistant's turn is over.Release the connection and wait for the patient's next message.
conversation.endThe assistant ended the conversation. reason explains why when supplied.Record the end and its reason, and return the patient to the right flow in your product.
ai_consultation.completeThe consultation is complete. artifacts holds the consultation document IDs.Store each non-null ID, retrieve the consultation documents, and offer appointments if your product supports them.

Only ai_consultation.complete marks the consultation complete, and only then does the chat become ready for appointment booking. If the consultation can't be finalized, the stream sends an error event instead; see stream errors.

Restore a transcript

To show a returning patient their history, list their chats and then the messages in one chat.

curl --fail-with-body --request GET \
  "$DOCTRONIC_API_URL/chats/$DOCTRONIC_CHAT_ID/messages/?page=1&size=50" \
  --header "Authorization: Bearer $DOCTRONIC_API_TOKEN" \
  --header "X-Doctronic-User-ID: $DOCTRONIC_USER_ID"
Representative response
{
  "code": "success",
  "message": "Request successful",
  "data": {
    "items": [
      {
        "role": "user",
        "content": "I have had a sore throat and fever since yesterday.",
        "messageId": "ac_hist_01K6JGHK3P2R5T7V9X1Z3B5D7F",
        "createdAt": "2026-08-31T14:32:00Z"
      },
      {
        "role": "assistant",
        "content": "I can help with that.",
        "messageId": "ac_hist_01K6JGHM5S4W7T9Y2B3C4D5E6F",
        "createdAt": "2026-08-31T14:32:04Z"
      }
    ],
    "total": 2,
    "page": 1,
    "size": 50,
    "pages": 1,
    "hasNext": false,
    "hasPrev": false
  }
}
OperationReturnsPaging and order
GET /chats/The user's chats: id and createdAtpage (from 1), size (1–100, default 20), orderBy (createdAt or updatedAt, - prefix for descending, default -createdAt)
GET /chats/{chatId}/messages/The chat's user and assistant messages: role, content, messageId, createdAtpage, size as above; oldest first by default, orderBy=-createdAt for newest first

Both responses page with total, pages, hasNext, and hasPrev. The message list holds text only, so store the events you act on, such as guardian events and artifact IDs, when they arrive.

The transcript is also how you recover from an interrupted stream: read it to see what the assistant already sent before you let the patient resend a turn.

Limits

  • One stream runs per chat at a time. A second request while a turn is open returns 409.
  • Chats carry only an id and createdAt. Keep any other consultation metadata in your system.
  • The API has no callback or routing endpoint. Orchestration after an event stays in your product.

On this page