Log in

Chat API

Use the Chat API to talk to your bot from your own app, backend or integration: start a conversation, send messages, stream replies and read back the history.

â„šī¸

Before you start

You need the API channel turned on for your bot, its Channel ID and a Secret key. The API channel is on the Standard, Pro and Enterprise plans. See API overview for how to get them, the base URL, versions and error codes.

All examples use version 2.0. Every endpoint below also works on 1.0, with identical requests and responses except for streaming.

Endpoints

MethodPathWhat it does
POST/api/public/channels/{channelId}/2.0/conversationStart a conversation
POST/api/public/channels/{channelId}/2.0/messageSend a message, continue a conversation or stream the reply
POST/api/public/channels/{channelId}/2.0/conversationsList conversations
POST/api/public/channels/{channelId}/2.0/messagesList a conversation's messages

Start a conversation

Creates a conversation and returns its conversationId and the bot's welcome message. Start here if you want to attach user data to the conversation, or if you'll stream replies.

POST /api/public/channels/{channelId}/2.0/conversation

Request body

The body is optional.

FieldTypeDescription
userDataobjectOptional. Details about the person chatting, such as name, email or your own id. Saved on the conversation. See Pass user data
messagestringOptional. Included as initialMessage in the Conversation started webhook. It isn't sent to the bot: use Send a message for that
curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/conversation \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "userData": { "id": "user_1234", "name": "Alex Doe", "email": "alex@example.com" }
  }'
const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/2.0/conversation`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({
      userData: { id: "user_1234", name: "Alex Doe", email: "alex@example.com" },
    }),
  },
);
const { conversationId } = await response.json();
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/conversation"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}
payload = {
    "userData": {"id": "user_1234", "name": "Alex Doe", "email": "alex@example.com"}
}

response = requests.post(url, json=payload, headers=headers)
conversation_id = response.json()["conversationId"]

Response

FieldTypeDescription
successbooleantrue when the conversation was created
conversationIdstringUse this in later requests to continue the conversation
responsestringThe bot's welcome message
{
  "success": true,
  "conversationId": "7bb301ad-e691-4341-8afb-b4407c4ffdf2",
  "response": "Hello, how can I help you?"
}

If a token limit has been reached, you get 429 with a plain statusMessage. See Usage limits.

Send a message

Sends a message to the bot and returns its full reply. If you don't include a conversationId, a new conversation is created and its id is returned, so you can continue it later.

POST /api/public/channels/{channelId}/2.0/message

Request body

FieldTypeDescription
messagestringRequired. The message to the bot
conversationIdstringOptional. Continue an existing conversation. Without it, a new conversation is started
streambooleanOptional. Set to true to stream the reply
extendSystemMessagestringOptional. Extra instructions added to the bot's instructions, for this request only. Ignored if overrideSystemMessage is set
overrideSystemMessagestringOptional. Replaces the bot's instructions, for this request only
extendContextobjectOptional. Extra information for the bot, for this request only. See Add context
userDataobjectOptional. id (string or number), name, email, tel. See Pass user data
curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/message \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "message": "What are your opening hours?"
  }'
const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/2.0/message`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({ message: "What are your opening hours?" }),
  },
);
const { conversationId, response: reply } = await response.json();
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/message"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}

response = requests.post(url, json={"message": "What are your opening hours?"}, headers=headers)
data = response.json()

A request using the optional fields:

curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/message \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "message": "Can I change my delivery date?",
    "extendSystemMessage": "Keep answers under three sentences.",
    "extendContext": { "custom": { "plan": "Pro", "orderCount": 3 } }
  }'
await fetch(`https://app.chatthing.ai/api/public/channels/${channelId}/2.0/message`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secretKey,
  },
  body: JSON.stringify({
    message: "Can I change my delivery date?",
    extendSystemMessage: "Keep answers under three sentences.",
    extendContext: { custom: { plan: "Pro", orderCount: 3 } },
  }),
});
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/message"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}
payload = {
    "message": "Can I change my delivery date?",
    "extendSystemMessage": "Keep answers under three sentences.",
    "extendContext": {"custom": {"plan": "Pro", "orderCount": 3}},
}

response = requests.post(url, json=payload, headers=headers)

Response

FieldTypeDescription
successbooleantrue when the bot replied
conversationIdstringThe conversation this message belongs to
responsestringThe bot's reply
{
  "success": true,
  "conversationId": "ea74ba3d-6645-4a88-92d1-7f78b1f83688",
  "response": "We're open 9am to 5pm, Monday to Friday."
}

Errors

CodeWhen
400message is missing, or a field has the wrong type
403The secret key doesn't match this channel
404The channel isn't found or is turned off, or the conversationId doesn't belong to this bot
429A token limit was reached, or the message is longer than the bot's Maximum message length. The body has a stable code; see Usage limits

Continue a conversation

Send the conversationId you got back from an earlier request. The bot then sees the earlier messages in the conversation, so it can answer follow-up questions.

curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/message \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "conversationId": "ea74ba3d-6645-4a88-92d1-7f78b1f83688",
    "message": "And on bank holidays?"
  }'
await fetch(`https://app.chatthing.ai/api/public/channels/${channelId}/2.0/message`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secretKey,
  },
  body: JSON.stringify({
    conversationId: "ea74ba3d-6645-4a88-92d1-7f78b1f83688",
    message: "And on bank holidays?",
  }),
});
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/message"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}
payload = {
    "conversationId": "ea74ba3d-6645-4a88-92d1-7f78b1f83688",
    "message": "And on bank holidays?",
}

response = requests.post(url, json=payload, headers=headers)

The response has the same shape as Send a message.

Stream replies

Add "stream": true to a Send a message request to receive the reply in pieces as it's written, instead of waiting for the whole answer. Your users see text sooner.

The stream format depends on the version in the URL. Use 2.0: streaming on 1.0 is deprecated.

Streaming on version 2.0

POST /api/public/channels/{channelId}/2.0/message
curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/message \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "message": "What are your opening hours?",
    "stream": true
  }'

The response is a Server-Sent Events stream (Content-Type: text/event-stream). Each event is one data: {json} line holding one chunk, and the stream ends with data: [DONE].

The conversation id is in the x-conversation-id response header, and also in a data-conversation chunk at the start of the stream. Use either to continue the conversation.

Chunk typeWhat it means
startThe reply has started. Carries a messageId
data-conversation{ "conversationId": "..." }, sent straight after start
text-start, text-delta, text-endThe reply text. Join the delta values of the text-delta chunks
tool-input-start, tool-input-delta, tool-input-availableThe bot is using a power-up, and the input it's sending
tool-output-availableThe power-up's result. output holds { "message": "...", "display": ... }, where display is an optional rich display (cards, tables and so on)
data-turn{ "rowIds": [...], "lastRowId": "...", "sources": [...] }, sent once the turn's messages are saved
finishThe reply finished successfully. Always comes after data-turn
error, data-errorThe reply failed. error has a readable message; data-error has { "provider", "model", "statusCode", "statusMessage" }. No finish follows

You may also see start-step and finish-step chunks, which you can ignore.

Example stream:

data: {"type":"start","messageId":"5f3c0c4e-8e1a-4f44-9a7b-2f1d3e4c5b6a"}

data: {"type":"data-conversation","data":{"conversationId":"ea74ba3d-6645-4a88-92d1-7f78b1f83688"}}

data: {"type":"start-step"}

data: {"type":"text-start","id":"txt-1"}

data: {"type":"text-delta","id":"txt-1","delta":"We're open 9am to 5pm,"}

data: {"type":"text-delta","id":"txt-1","delta":" Monday to Friday."}

data: {"type":"text-end","id":"txt-1"}

data: {"type":"finish-step"}

data: {"type":"data-turn","data":{"rowIds":["9df33cfc-1bc5-4990-8aa2-94552aa646c4"],"lastRowId":"9df33cfc-1bc5-4990-8aa2-94552aa646c4","sources":[]}}

data: {"type":"finish"}

data: [DONE]

Reading the stream in JavaScript:

const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/2.0/message`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({ message, stream: true }),
  },
);

const conversationId = response.headers.get("x-conversation-id");
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  // events are separated by a blank line
  const events = buffer.split("\n\n");
  buffer = events.pop() ?? "";

  for (const event of events) {
    const payload = event.replace(/^data: /, "").trim();
    if (!payload || payload === "[DONE]") continue;
    const chunk = JSON.parse(payload);
    if (chunk.type === "text-delta") {
      process.stdout.write(chunk.delta);
    }
  }
}

If you build with JavaScript, the ai npm package can read this stream for you (for example with readUIMessageStream), so you don't have to parse it by hand.

If a token limit has been reached, you get a normal (not streamed) 429 response with the usage limit body.

Streaming on version 1.0 (deprecated)

🚨

Deprecated

Streaming on version 1.0 still works but is deprecated and will be removed in a future release. Move to version 2.0 streaming.

On /api/public/channels/{channelId}/1.0/message with "stream": true, the reply is newline-delimited JSON: one OpenAI-style chat.completion.chunk object per line. Join the choices[0].delta.content values to build the reply. The stream ends when the response closes. The conversation id isn't included in this format, so start a conversation first if you need it.

{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1760000000,"model":"model-id","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1760000000,"model":"model-id","choices":[{"index":0,"delta":{"content":"We're open"},"finish_reason":null}]}
{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1760000000,"model":"model-id","choices":[{"index":0,"delta":{"content":" 9am to 5pm."},"finish_reason":null}]}
{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1760000000,"model":"model-id","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

Reading the 1.0 stream in JavaScript (the parser also tolerates a data: prefix and a [DONE] line, so it works with earlier examples of this format):

const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/1.0/message`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({ message, stream: true }),
  },
);

const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  const lines = buffer.split("\n");
  buffer = lines.pop() ?? "";

  for (const line of lines) {
    const payload = line.replace(/^data: /, "").trim();
    if (!payload || payload === "[DONE]") continue;
    const content = JSON.parse(payload).choices[0]?.delta?.content;
    if (content) process.stdout.write(content);
  }
}

Pass user data

User data tells the bot who it's talking to, so it can personalise answers, and passes those details on to your webhooks.

  • When you start a conversation with Start a conversation, userData is saved on the conversation. The bot sees it on every reply, it appears with the conversation in Chat Thing, and it's included in the Conversation started and Conversation escalated webhooks. This is the recommended way.
  • On Send a message, userData accepts id, name, email and tel. When that request creates a new conversation, the details are included in the Conversation started webhook, but they aren't saved on the conversation. To make the details available to the bot, send them when you start the conversation.

User data is whatever your code sends. Chat Thing doesn't check it, so don't rely on it to prove who someone is.

Add context

extendContext gives the bot extra information for one request, such as details of the page or account the user is looking at. It isn't saved on the conversation.

FieldTypeDescription
customobjectYour own key-value pairs. Values must be strings, numbers or true/false
pageMetaobjectDetails of a web page: url (required), and optionally title, description, og:title and og:description
markdownstringAny extra text, such as the content of the page
{
  "message": "Is this in stock?",
  "extendContext": {
    "pageMeta": { "url": "https://example.com/products/blue-mug", "title": "Blue mug" },
    "custom": { "sku": "MUG-BLUE", "stock": 12 }
  }
}

extendSystemMessage and overrideSystemMessage also apply to one request only. Anyone with your secret key can use them to change the bot's instructions, which is another reason to keep the key on your server.

List conversations

Returns the bot's conversations, oldest first, a page at a time.

POST /api/public/channels/{channelId}/2.0/conversations

Request body

FieldTypeDescription
limitintegerOptional. Conversations per page, from 1 to 100. Default 10
cursorstringOptional. To get the next page, send the pagination.last value from the previous response. Leave it out for the first page
curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/conversations \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{ "limit": 2 }'
const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/2.0/conversations`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({ limit: 2 }),
  },
);
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/conversations"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}

response = requests.post(url, json={"limit": 2}, headers=headers)

Response

FieldTypeDescription
successbooleantrue on success
conversationsarrayEach has id, createdAt (ISO 8601) and initialMessage (the first message after the bot's welcome message, or null)
pagination.totalintegerTotal number of conversations for this bot
pagination.laststring or nullThe id of the last conversation on this page. Send it as cursor to get the next page. null when the page is empty
{
  "success": true,
  "conversations": [
    {
      "id": "3eda9841-9813-49a4-a75c-3758ea31b43c",
      "createdAt": "2026-01-16T10:07:04.296Z",
      "initialMessage": "What are your opening hours?"
    },
    {
      "id": "9b03aeb0-d9fe-466d-9fc9-49607643ade7",
      "createdAt": "2026-01-16T10:07:24.458Z",
      "initialMessage": "Do you deliver to Ireland?"
    }
  ],
  "pagination": {
    "total": 15,
    "last": "9b03aeb0-d9fe-466d-9fc9-49607643ade7"
  }
}

Page through results

Keep requesting with cursor set to the previous pagination.last until a page comes back with fewer items than your limit:

let cursor;
do {
  const res = await fetch(`https://app.chatthing.ai/api/public/channels/${channelId}/2.0/conversations`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-API-Secret-Key": secretKey },
    body: JSON.stringify({ limit: 100, cursor }),
  });
  const { conversations, pagination } = await res.json();
  // ...use conversations
  cursor = conversations.length === 100 ? pagination.last : undefined;
} while (cursor);

List a conversation's messages

Returns the messages in one conversation, oldest first, a page at a time.

POST /api/public/channels/{channelId}/2.0/messages

Request body

FieldTypeDescription
conversationIdstringRequired. The conversation to read
limitintegerOptional. Messages per page, from 1 to 100. Default 10
cursorstringOptional. To get the next page, send the pagination.last value from the previous response. Leave it out for the first page
curl --request POST \
  --url https://app.chatthing.ai/api/public/channels/{channelId}/2.0/messages \
  --header 'Content-Type: application/json' \
  --header 'X-API-Secret-Key: {secretKey}' \
  --data '{
    "conversationId": "{conversationId}",
    "limit": 10
  }'
const response = await fetch(
  `https://app.chatthing.ai/api/public/channels/${channelId}/2.0/messages`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Secret-Key": secretKey,
    },
    body: JSON.stringify({ conversationId, limit: 10 }),
  },
);
import requests

url = f"https://app.chatthing.ai/api/public/channels/{channel_id}/2.0/messages"
headers = {
    "Content-Type": "application/json",
    "X-API-Secret-Key": secret_key,
}
payload = {"conversationId": conversation_id, "limit": 10}

response = requests.post(url, json=payload, headers=headers)

Response

FieldTypeDescription
successbooleantrue on success
messagesarrayEach has id, message, role and createdAt (ISO 8601)
pagination.totalintegerTotal number of messages in the conversation
pagination.laststring or nullThe id of the last message on this page. Send it as cursor to get the next page

role is usually user or assistant. Power-up activity can appear as other roles, such as tool. Keep only user and assistant if you want the transcript your user saw.

{
  "success": true,
  "messages": [
    {
      "id": "9df33cfc-1bc5-4990-8aa2-94552aa646c4",
      "message": "Hello, how can I help you?",
      "role": "assistant",
      "createdAt": "2026-01-16T10:07:20.127Z"
    },
    {
      "id": "4364a927-ca22-4bab-ac17-48903a514717",
      "message": "Do you deliver to Ireland?",
      "role": "user",
      "createdAt": "2026-01-16T10:07:24.324Z"
    },
    {
      "id": "b5ce7f56-9340-4072-8716-793f720f6fb1",
      "message": "Yes, we deliver to Ireland in 3 to 5 working days.",
      "role": "assistant",
      "createdAt": "2026-01-16T10:07:26.518Z"
    }
  ],
  "pagination": {
    "total": 3,
    "last": "b5ce7f56-9340-4072-8716-793f720f6fb1"
  }
}

If the conversation doesn't exist or belongs to another bot, you get 404.

  • API overview

    Base URL, API keys, authentication headers, versions, error codes and usage limits for the Chat Thing REST API.

  • Webhooks

    Send Chat Thing events such as new conversations, escalations and syncs to your own systems, Slack or Discord, and trigger data source syncs from a URL.

Last updated