Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Chatbot API Flow

This guide explains how the public chatbot API fits together from an integrator's point of view. It focuses on the flow and data model so you can build a client without guessing how responses should be interpreted.

For the full schema, field-level validation, and complete endpoint reference, see the API reference.

Flow at a glance

  1. Your backend calls POST /api/v1/chat/auth with the chatbot API key and a chatbot_id.
  2. The API returns a visitor Bearer token.
  3. Your client calls GET /api/v1/chat/config to load branding and startup configuration.
  4. Your client sends user input to POST /api/v1/chat/messages and receives a streamed response.
  5. The stream emits structured content as part_delta and part events, then ends with done or error.
  6. If the assistant returns an actionable marker, your client collects the required data and sends it to POST /api/v1/chat/actions.
  7. You can optionally read message history, save a conversation rating, or delete the visitor's data.

Auth and config

The API has two authentication layers:

StepWho calls itPurpose
POST /api/v1/chat/authYour backendExchange the API key for a visitor token
Visitor-scoped endpointsYour client or trusted backendUse the returned Bearer token for config, messages, actions, history, rating, and deletion

Keep the API key on your server. Do not expose it in browser code, mobile apps, or public frontend bundles.

/auth uses HTTP Basic Auth with:

  • the API key as the username
  • an empty password

When the token expires, call /auth again to create a new visitor token. There is no refresh token flow.

Example: create a visitor token

curl -X POST "https://api.dialogintelligens.dk/api/v1/chat/auth" \
  -u "$CHATBOT_API_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "chatbot_id": "shop-bot"
  }'

The response contains a JWT token:

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

After that, your client can load config with the Bearer token. GET /api/v1/chat/config returns the public UI configuration for the chatbot, including:

  • name
  • avatar
  • welcome message
  • theme colors
  • popup messages

How message data is structured

Every conversation item is a message. A message contains ordered parts.

For user input, message.parts is how you send text and attachments in one request.

For assistant output, message.parts is also how you render the response. Parts are already structured for you, so the client should render them directly instead of trying to parse raw text.

Mental model

TermMeaning
messageOne user, assistant, or system entry in the conversation
partA top-level renderable unit inside a message
blockA section inside a rich_text part or table cell
spanInline formatted text inside a paragraph or bullet item
message
`-- parts[]
    |-- rich_text
    |   `-- blocks[]
    |       |-- paragraph
    |       |   `-- spans[] -> text | bold | strike | link
    |       `-- bullet_list
    |-- image
    |-- table
    |-- products
    `-- show_contact_form

Parts

Common assistant part types are:

  • rich_text for formatted text
  • image for image content
  • table for structured tables
  • products for product cards
  • marker parts such as show_contact_form or request_image_upload

The important detail is that all of these are siblings in the same parts array. That means a single assistant message can look like:

  1. Some text
  2. A marker telling the client to open a form
  3. More text after the marker

Blocks and spans

Inside a rich_text part:

  • blocks describe larger sections such as paragraphs and bullet lists
  • spans describe inline formatting inside those blocks, such as plain text, bold text, struck text, and links

This lets a client render formatted content without having to parse markdown or custom marker syntax.

Example: a message with text and an action

{
  "message_id": "msg_123",
  "role": "assistant",
  "parts": [
    {
      "type": "rich_text",
      "part_id": "part_1",
      "blocks": [
        {
          "type": "paragraph",
          "spans": [{ "type": "text", "text": "Need help with your order?" }]
        }
      ]
    },
    {
      "type": "show_contact_form",
      "part_id": "part_2",
      "fields": [
        { "key": "name", "label": "Your name", "required": true },
        { "key": "email", "label": "Email address", "type": "email", "required": true }
      ]
    },
    {
      "type": "rich_text",
      "part_id": "part_3",
      "blocks": [
        {
          "type": "paragraph",
          "spans": [{ "type": "text", "text": "Fill in the form and we will follow up." }]
        }
      ]
    }
  ]
}

In this example, the form is not separate from the message. It is one ordered part of the message.

How streaming works

POST /api/v1/chat/messages returns a Server-Sent Events stream. Because this is a POST endpoint, the browser's built-in EventSource API is not a good fit. Use fetch() directly or a helper such as @microsoft/fetch-event-source.

The stream uses five public event types:

EventMeaning
statusLifecycle updates such as connected or processing
part_deltaIncremental updates for a streamed part
partA finalized structured part
doneThe final assembled assistant message
errorA terminal stream error

Typical flow:

status(connected)
status(processing)
part_delta / part ...
done or error

part_delta is useful when you want progressive rendering for content such as:

  • rich text
  • products
  • tables

part gives you a finalized structured part. Marker parts typically arrive this way because they do not need progressive updates.

done.message is the final source of truth for the assistant response. If you rendered draft content while streaming, replace or reconcile it with the message from done.

Example: consume the message stream

import { fetchEventSource } from "@microsoft/fetch-event-source";
 
await fetchEventSource("https://api.dialogintelligens.dk/api/v1/chat/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message: {
      parts: [{ type: "text", text: "What is your return policy?" }],
    },
    context: {
      page: window.location.href,
    },
  }),
  onmessage(event) {
    const data = JSON.parse(event.data);
 
    switch (event.event) {
      case "status":
        updateStatus(data.status);
        break;
      case "part_delta":
        applyPartDelta(data.part_id, data.delta);
        break;
      case "part":
        renderFinalPart(data.part);
        break;
      case "done":
        replaceDraftMessage(data.message);
        break;
      case "error":
        showError(data.message);
        break;
    }
  },
});

How markers and actions work together

Markers are assistant parts that tell the client to do something beyond rendering text.

The key link is part_id:

  • the assistant sends a marker part with a part_id
  • your client renders the related UI
  • when the user completes the action, your client sends that same part_id to /actions

Marker behavior

Marker partWhat the client doesFollow-up
show_contact_formRender the fields from the markerSubmit action.type = "contact_form" to /actions
show_support_ticketRender the ticket form and attachment rules from the markerSubmit action.type = "support_ticket" to /actions
request_image_uploadPrompt the user to upload an image that matches the marker constraintsSend a later /messages request with an image part
request_human_agentShow a handoff option in your UIClient-defined flow
customHandle your own key and optional payloadClient-defined flow

Only show_contact_form and show_support_ticket are submitted to POST /api/v1/chat/actions.

Example: submit an action

{
  "part_id": "part_2",
  "action": {
    "type": "contact_form",
    "fields": {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "message": "I need help with my order"
    }
  }
}

The same pattern applies to support_ticket, but with the support ticket payload shape defined in the API reference.

History, feedback, and deletion

Once a visitor is authenticated, you can also use:

  • GET /api/v1/chat/messages to load paginated message history
  • POST /api/v1/chat/rate to save a 1-5 conversation rating and optional feedback
  • DELETE /api/v1/chat to remove all data for the current visitor

These endpoints use the same Bearer token as config, messages, and actions.