Repository navigation
API Reference Live Chat
A person on the team answers instead of the assistant: reply, take, give, close and hand back a live chat, and see who is available. Part of the API reference: setup, shared types and errors are there.
-
Live chat now:
GET /live/status -
Reply in a live chat:
POST /conversations/:id/reply -
Take a conversation over:
POST /conversations/:id/takeover -
Take or give a conversation:
POST /conversations/:id/assign -
Close a conversation:
POST /conversations/:id/close -
Hand back to the assistant:
POST /conversations/:id/handback -
Telegram:
GET /live/telegram -
Connect Telegram:
POST /live/telegram -
Change Telegram options:
PATCH /live/telegram -
Send a Telegram test:
POST /live/telegram/test -
Disconnect Telegram:
DELETE /live/telegram
GET /live/status · scope conversations:read
Whether live chat is on, who on the team can take chats (an open dashboard set to available), whether Telegram is linked, and how many live chats there are: unassigned, waiting for a reply, and waiting for you.
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/live/status" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/status`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as LiveChatNowResponse;Response 200
{
"enabled": true,
"hub": true,
"available": 1,
"agents": [
{
"email": "sam@acme.example",
"name": "Sam",
"available": true
}
],
"telegram": {
"connected": true,
"linked": true
},
"live": 2,
"unassigned": 1,
"waiting": 1,
"mine": 0
}type LiveChatNowResponse = {
enabled: boolean;
hub: boolean;
available: number;
agents: Array<{
email: string;
name: string;
available: boolean;
}>;
telegram: {
connected: boolean;
linked: boolean;
};
live: number;
unassigned: number;
waiting: number;
mine: number;
};POST /conversations/:id/reply · scope conversations:write
A person on the team answers the visitor (shown with their first name, live.showAgentName). Any conversation: replying to one the assistant has, or a closed one, takes it over first (it becomes live). Needs live chat on. Sends message.sent with author.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
| Body field | Required | Description |
|---|---|---|
text |
yes | Up to 4000 characters. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/reply" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Hi Ada, Sam here. Tomorrow at 9 works — shall I book it?"
}'type ReplyInLiveChatRequest = {
/** Up to 4000 characters. */
text: string;
};
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/reply`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Hi Ada, Sam here. Tomorrow at 9 works — shall I book it?'
} satisfies ReplyInLiveChatRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ReplyInLiveChatResponse;Response 201
{
"id": "h_mfx2k1a9",
"ts": 1760000000000,
"role": "agent",
"type": "text",
"text": "Hi Ada, Sam here. Tomorrow at 9 works — shall I book it?",
"meta": {
"human": true,
"agentName": "Sam"
}
}type ReplyInLiveChatResponse = {
id: string;
ts: number;
role: string;
type: string;
text: string;
meta: {
human: boolean;
agentName: string;
};
};Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request |
Live chat is off, or the message is empty. |
POST /conversations/:id/takeover · scope conversations:write
A signed-in person takes the chat, whatever it is doing: from the assistant, from a colleague, or closed (this reopens it). It becomes live and theirs; the visitor sees who joined, at once if their chat is open. For a key, reply instead (POST /conversations/:id/reply takes it over too). Sends conversation.assigned.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/takeover" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/takeover`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as TakeConversationOverResponse;Response 200
{
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "live",
"assignedTo": "sam@acme.example",
"assignedName": "Sam"
}type TakeConversationOverResponse = {
conversationId: string;
status: string;
assignedTo: string;
assignedName: string | null;
};Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request |
Live chat is off, or called with an API key (use reply). |
POST /conversations/:id/assign · scope conversations:write
to: "me" (a signed-in person takes it), a team member's email (give it; admins only), or null (unassign). The visitor of a live chat sees who joined. Sends conversation.assigned.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
| Body field | Required | Description |
|---|---|---|
to |
yes |
"me", an email on the team, or null. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/assign" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "sam@acme.example"
}'type TakeGiveConversationRequest = {
/** `"me"`, an email on the team, or null. */
to: string;
};
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/assign`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'sam@acme.example'
} satisfies TakeGiveConversationRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as TakeGiveConversationResponse;Response 200
{
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"assignedTo": "sam@acme.example",
"assignedName": "Sam"
}type TakeGiveConversationResponse = {
conversationId: string;
assignedTo: string | null;
assignedName: string | null;
};POST /conversations/:id/close · scope conversations:write
Closes it (conversations also close after live.closeAfterMinutes without a message). The visitor of a live chat is told; if they write again, the assistant answers. Sends conversation.closed.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/close" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/close`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as CloseConversationResponse;Response 200
{
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "closed",
"closedAt": 1760000000000
}type CloseConversationResponse = {
conversationId: string;
status: string;
closedAt: number;
};POST /conversations/:id/handback · scope conversations:write
Ends the live part, or reopens a closed conversation for the assistant: it answers the visitor's next message. The team can take it over again at any time. Sends handover.ended.
| Parameter | In | Required | Description |
|---|---|---|---|
id |
path | yes | The record's id. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/handback" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/handback`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as HandBackToAssistantResponse;Response 200
{
"conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
"status": "bot"
}type HandBackToAssistantResponse = {
conversationId: string;
status: string;
};Errors
| Status | Code | When |
|---|---|---|
| 409 | conflict |
The assistant already has it. |
GET /live/telegram · scope settings:read
Whether a Telegram bot is connected and linked to a chat; until linked, the linkCode to send there as /link <code>.
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl "$HELPPUFF_URL/api/v1/live/telegram" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/telegram`, {
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as TelegramResponse;Response 200
{
"connected": true,
"linked": true,
"bot": {
"name": "Acme Desk",
"username": "acme_desk_bot"
},
"chat": {
"title": "Acme team",
"topics": true
},
"linkCode": null,
"shareContact": true,
"status": "ok",
"lastError": null,
"updatedAt": 1760000000000
}type TelegramResponse = {
connected: boolean;
linked: boolean;
bot: {
name: string;
username: string;
} | null;
chat: {
title: string;
topics: boolean;
} | null;
linkCode: string | null;
shareContact: boolean;
status: string | null;
lastError: string | null;
updatedAt: number | null;
};POST /live/telegram · scope settings:write
Connects the bot made with @BotFather by its token (checked with getMe, stored encrypted), and points its webhook here. Then add the bot to your team's group (Topics on, the bot an admin with "Manage topics"), or open a private chat with it, and send /link <linkCode> there.
| Body field | Required | Description |
|---|---|---|
token |
yes | The bot token from @BotFather. |
Request
curl -X POST "$HELPPUFF_URL/api/v1/live/telegram" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "7123456789:AAH3k…"
}'type ConnectTelegramRequest = {
/** The bot token from @BotFather. */
token: string;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/telegram`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: '7123456789:AAH3k…'
} satisfies ConnectTelegramRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ConnectTelegramResponse;Response 201
{
"connected": true,
"linked": false,
"bot": {
"name": "Acme Desk",
"username": "acme_desk_bot"
},
"chat": null,
"linkCode": "9f3a1c2e",
"shareContact": true,
"status": "linking",
"lastError": null,
"updatedAt": 1760000000000
}type ConnectTelegramResponse = {
connected: boolean;
linked: boolean;
bot: {
name: string;
username: string;
} | null;
chat: string | null;
linkCode: string | null;
shareContact: boolean;
status: string | null;
lastError: string | null;
updatedAt: number | null;
};Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request |
Telegram refused the token, or the webhook. |
PATCH /live/telegram · scope settings:write
| Body field | Required | Description |
|---|---|---|
shareContact |
no | Include visitors' email and phone in what goes to Telegram. |
Request
curl -X PATCH "$HELPPUFF_URL/api/v1/live/telegram" \
-H "Authorization: Bearer $HELPPUFF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shareContact": false
}'type ChangeTelegramOptionsRequest = {
/** Include visitors' email and phone in what goes to Telegram. */
shareContact?: boolean;
};
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/telegram`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
shareContact: false
} satisfies ChangeTelegramOptionsRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ChangeTelegramOptionsResponse;Response 200
{
"connected": true,
"linked": true,
"bot": {
"name": "Acme Desk",
"username": "acme_desk_bot"
},
"chat": {
"title": "Acme team",
"topics": true
},
"linkCode": null,
"shareContact": false,
"status": "ok",
"lastError": null,
"updatedAt": 1760000000000
}type ChangeTelegramOptionsResponse = {
connected: boolean;
linked: boolean;
bot: {
name: string;
username: string;
} | null;
chat: {
title: string;
topics: boolean;
} | null;
linkCode: string | null;
shareContact: boolean;
status: string | null;
lastError: string | null;
updatedAt: number | null;
};POST /live/telegram/test · scope settings:write
Request
curl -X POST "$HELPPUFF_URL/api/v1/live/telegram/test" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/telegram/test`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as SendTelegramTestResponse;Response 200
{
"ok": true
}type SendTelegramTestResponse = {
ok: boolean;
};Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request |
No chat is linked yet. |
DELETE /live/telegram · scope settings:write
| Parameter | In | Required | Description |
|---|---|---|---|
site |
query | no | The site (a key always uses its own). |
Request
curl -X DELETE "$HELPPUFF_URL/api/v1/live/telegram" \
-H "Authorization: Bearer $HELPPUFF_API_KEY"const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/live/telegram`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
},
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as DisconnectTelegramResponse;Response 200
{
"connected": false
}type DisconnectTelegramResponse = {
connected: boolean;
};Start
Tutorials
- All tutorials
- Order tracking
- Send chats to your CRM
- Nearest store
- Verified account changes
- Logged-in customer support
- Product and stock
- Hot leads to Slack
- Knowledge base or tool?
- Testing and monitoring
- Why the AI never books
Configure
- Configuration
- Configuration reference
- CLI reference
- Knowledge base
- Prompt and instructions
- Tools: your APIs in the chat
- Agent files: import and export
- Signed-in visitors
- The widget
- The playground
- Leads and callbacks
- Jobs
- Photos and files
- Bookings and the calendar
- Integrations
- Live chat
- Telegram
- Webhooks
- Email notifications
API
API reference
- Overview
- Account
- Chat
- Conversations
- Live chat
- Labels and notes
- Leads
- Callbacks
- Jobs
- Appointments
- Integrations
- Knowledge
- Prompt
- Tools
- Settings
- Webhooks
- Analytics
- Team
- API keys
- Audit
Providers
- Overview
- Models and providers
- Custom model
- Custom knowledge base
- Workers AI (default)
- AI models: speed, quality, cost
- Cloudflare AI Search
- OpenAI
- Gemini
- Anthropic Claude
- Retell
- Your own API
Operate
- Deployment
- Turn on Turnstile
- Upgrading
- Costs and limits
- Cloudflare Free plan limits
- Security
- Troubleshooting
Build