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
| Method | Path | What it does |
|---|---|---|
POST | /api/public/channels/{channelId}/2.0/conversation | Start a conversation |
POST | /api/public/channels/{channelId}/2.0/message | Send a message, continue a conversation or stream the reply |
POST | /api/public/channels/{channelId}/2.0/conversations | List conversations |
POST | /api/public/channels/{channelId}/2.0/messages | List 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.
| Field | Type | Description |
|---|---|---|
userData | object | Optional. Details about the person chatting, such as name, email or your own id. Saved on the conversation. See Pass user data |
message | string | Optional. 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" }
}'
Response
| Field | Type | Description |
|---|---|---|
success | boolean | true when the conversation was created |
conversationId | string | Use this in later requests to continue the conversation |
response | string | The 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
| Field | Type | Description |
|---|---|---|
message | string | Required. The message to the bot |
conversationId | string | Optional. Continue an existing conversation. Without it, a new conversation is started |
stream | boolean | Optional. Set to true to stream the reply |
extendSystemMessage | string | Optional. Extra instructions added to the bot's instructions, for this request only. Ignored if overrideSystemMessage is set |
overrideSystemMessage | string | Optional. Replaces the bot's instructions, for this request only |
extendContext | object | Optional. Extra information for the bot, for this request only. See Add context |
userData | object | Optional. 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?"
}'
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 } }
}'
Response
| Field | Type | Description |
|---|---|---|
success | boolean | true when the bot replied |
conversationId | string | The conversation this message belongs to |
response | string | The bot's reply |
{
"success": true,
"conversationId": "ea74ba3d-6645-4a88-92d1-7f78b1f83688",
"response": "We're open 9am to 5pm, Monday to Friday."
}
Errors
| Code | When |
|---|---|
400 | message is missing, or a field has the wrong type |
403 | The secret key doesn't match this channel |
404 | The channel isn't found or is turned off, or the conversationId doesn't belong to this bot |
429 | A 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?"
}'
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 type | What it means |
|---|---|
start | The reply has started. Carries a messageId |
data-conversation | { "conversationId": "..." }, sent straight after start |
text-start, text-delta, text-end | The reply text. Join the delta values of the text-delta chunks |
tool-input-start, tool-input-delta, tool-input-available | The bot is using a power-up, and the input it's sending |
tool-output-available | The 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 |
finish | The reply finished successfully. Always comes after data-turn |
error, data-error | The 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,
userDatais 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,
userDataacceptsid,name,emailandtel. 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.
| Field | Type | Description |
|---|---|---|
custom | object | Your own key-value pairs. Values must be strings, numbers or true/false |
pageMeta | object | Details of a web page: url (required), and optionally title, description, og:title and og:description |
markdown | string | Any 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
| Field | Type | Description |
|---|---|---|
limit | integer | Optional. Conversations per page, from 1 to 100. Default 10 |
cursor | string | Optional. 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 }'
Response
| Field | Type | Description |
|---|---|---|
success | boolean | true on success |
conversations | array | Each has id, createdAt (ISO 8601) and initialMessage (the first message after the bot's welcome message, or null) |
pagination.total | integer | Total number of conversations for this bot |
pagination.last | string or null | The 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
| Field | Type | Description |
|---|---|---|
conversationId | string | Required. The conversation to read |
limit | integer | Optional. Messages per page, from 1 to 100. Default 10 |
cursor | string | Optional. 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
}'
Response
| Field | Type | Description |
|---|---|---|
success | boolean | true on success |
messages | array | Each has id, message, role and createdAt (ISO 8601) |
pagination.total | integer | Total number of messages in the conversation |
pagination.last | string or null | The 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.
Related
- 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