Overview
A Deskee chatbot answers customers from a business's own content and hands over to a person when it matters. Everything you can do in the dashboard you can also do from code:
- Chat API: your app, kiosk or automation sends the chatbot a message and gets its reply. Same knowledge, tools, handoff and Service Desk as the website widget.
- Management API: create a chatbot from a website, set its persona and look, teach it, publish it and get the embed code.
- MCP server: connect an AI assistant such as Claude or Cursor and just ask it to set up and run the chatbot.
The API is available on every plan. Base URL: https://deskee.io. Requests and answers are JSON.
Authentication
The account owner creates keys under Account → API keys. Send one with every request:
Authorization: Bearer dsk_…
Each key only does what it was given permission for when it was created:
| Permission | Scope | Allows |
|---|---|---|
| Send messages to chatbots through the API | chat | The chat API |
| See chatbots and their settings | chatbots:read | Reading chatbots, knowledge, embed code |
| Create chatbots and change their settings, knowledge and look | chatbots:write | The rest of the management API |
| Read conversations | conversations:read | Conversations on every channel |
| Reply to, hand back and close conversations | conversations:reply | Answering as the account owner |
| See calendars, open times and bookings / Book and cancel appointments | calendars:read, calendars:book | The calendars API |
Keep keys on your server. Never put one in a web page or an app people can download: for websites, use the chat widget. A key acts for the whole account, so revoke it straight away if it leaks.
Chat API
Switch the API on for a chatbot first: open it in Deskee, go to Channels → API and set it to Published (Draft switches it off). The Setup tab shows the chatbot's id.
Send a message
curl -X POST https://deskee.io/api/v1/chatbots/CHATBOT_ID/messages \
-H "Authorization: Bearer dsk_…" \
-H "Content-Type: application/json" \
-d '{"text":"What are your opening hours?","visitor":{"name":"Ada","email":"ada@example.com"}}'
{
"conversation_id": "5f0c…",
"status": "bot",
"reply": "We're open 8am to 4pm, Tuesday to Saturday.",
"messages": [
{ "id": "9be9…", "role": "bot", "text": "We're open 8am to 4pm, Tuesday to Saturday.", "author_name": null, "created": "2026-10-09T18:31:04.000Z" }
]
}
| Field | |
|---|---|
text | Required. Up to 4,000 characters. |
conversation_id | Carry on an earlier conversation. Leave it out to start a new one. |
language | Two-letter code for the reply language, if the chatbot speaks it. |
visitor | { name, email }, shown to your team in the Service Desk. |
The call waits for the chatbot's answer, usually a few seconds. reply is the chatbot's answer in Markdown; messages lists everything added after your message. status is bot, awaiting (waiting for a person), live (a person is answering) or closed. Each conversation counts towards the account's monthly AI conversations, like a website chat.
Replies from a person
When the chatbot hands over, status is live and reply is null: a teammate answers from the Service Desk later. Collect their replies with a webhook, or poll with the id of the last message you have:
curl "https://deskee.io/api/v1/conversations/CONVERSATION_ID/messages?after=MESSAGE_ID" \
-H "Authorization: Bearer dsk_…"
Webhooks
On the chatbot's Channels → API → Webhook tab, add an HTTPS address. Replies that arrive after your request has finished (a teammate's answer, "joined" and "closed" notes) are POSTed to it. The chatbot's own replies aren't: they're already in the answer to your request.
POST https://your-server.example.com/deskee
X-Deskee-Timestamp: 1791563464
X-Deskee-Signature: sha256=4c1e…
{ "event": "message", "conversation_id": "5f0c…",
"message": { "id": "a71d…", "role": "agent", "text": "Hi Ada, Sam here…", "author_name": "Sam", "created": "…" } }
Check each one came from Deskee: the signature is an HMAC-SHA256, keyed with the signing secret shown on the same tab, of the timestamp, a full stop and the raw request body. Send a test event on that tab posts { "event": "test" }.
// Node.js
const crypto = require("crypto");
const expected = "sha256=" + crypto.createHmac("sha256", SECRET)
.update(req.headers["x-deskee-timestamp"] + "." + rawBody).digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-deskee-signature"]));
# Python
import hmac, hashlib
expected = "sha256=" + hmac.new(SECRET.encode(), (timestamp + "." + raw_body).encode(), hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, signature)
Management API
Create and set up chatbots as you would in the dashboard: the same plan limits, defaults and checks apply. The only thing you can delete is a single knowledge source; deleting chatbots and conversations stays in the dashboard.
| Endpoint | Scope |
|---|---|
GET /api/v1/chatbots | chatbots:read |
GET /api/v1/chatbots/ID: persona, website widget with its embed code, channels, knowledge, crawl progress | chatbots:read |
POST /api/v1/chatbots { name, description?, website?, learn_website?, match_website_theme? } | chatbots:write |
PATCH /api/v1/chatbots/ID { name?, description?, website?, status? } | chatbots:write |
PATCH /api/v1/chatbots/ID/persona { name?, title?, welcome_message?, launcher_label?, tones?, profile_image_url?, instructions?, timezone?, languages? } | chatbots:write |
GET /api/v1/chatbots/ID/knowledge | chatbots:read |
POST /api/v1/chatbots/ID/knowledge { type: "text", label, content }, { type: "webpage", url } or { type: "website", url } | chatbots:write |
GET /api/v1/chatbots/ID/knowledge/SOURCE_ID, with its full text | chatbots:read |
PATCH /api/v1/chatbots/ID/knowledge/SOURCE_ID { label?, content? (text sources), refresh?: true (webpage and Facebook sources) } | chatbots:write |
DELETE /api/v1/chatbots/ID/knowledge/SOURCE_ID (not Continuous Learning) | chatbots:write |
PATCH /api/v1/chatbots/ID/website { domain?, status?, theme?: { brand_color?, header_color?, font?, position?, launcher_style?, open_by_default? } } | chatbots:write |
POST /api/v1/chatbots/ID/website/match-theme { url? } | chatbots:write |
POST /api/v1/chatbots/ID/website/preview-link { expires_in_days?: 0, 7, 14 or 30 } | chatbots:write |
A chatbot from a website, in one call
Give a website and Deskee reads the whole site into the chatbot's knowledge in the background (follow site_crawl on GET /api/v1/chatbots/ID), styles the widget in the site's colours and font, and locks it to that domain.
curl -X POST https://deskee.io/api/v1/chatbots \
-H "Authorization: Bearer dsk_…" -H "Content-Type: application/json" \
-d '{"name":"Harbour Bakery","website":"https://bakery.example.com"}'
Persona, knowledge and look
PATCH /api/v1/chatbots/ID/persona
{ "name": "Bella", "welcome_message": "Hi! How can I help?", "tones": ["friendly", "concise"] }
POST /api/v1/chatbots/ID/knowledge
{ "type": "text", "label": "This week", "content": "Special: bacon & brie sourdough with a coffee, £6.50 until Sunday." }
PATCH /api/v1/chatbots/ID/website
{ "theme": { "brand_color": "#C2410C", "font": "lato", "launcher_style": "pill" }, "status": "published" }
When something changes, such as a new weekly special, update the existing source (PATCH with new content) rather than adding another, so the chatbot doesn't see both. Reading a whole website tidies every page with AI, so an account can start 5 of those through the API a day; refreshing single pages is limited to 50 a day.
Persona updates change only the fields you send; text is in the chatbot's main language. Tones: friendly, professional, concise, playful, empathetic, formal, casual, enthusiastic, reassuring. Colours are hex codes: a full light and dark palette is built from them, and a colour too pale to build one from is refused. Launcher styles: bubble, edge_tab, peek, drawer, pill, ask_bar, chips, preview, dock, spotlight. Positions: bottom-right, bottom-left, top-right, top-left, middle-right, middle-left.
Conversations
Read and answer conversations from every channel: website, email, phone, Telegram, SMS and the chat API. Phone calls can only be read: to follow one up, call the customer back.
| Endpoint | Scope |
|---|---|
GET /api/v1/conversations?chatbot_id&status&before&limit, newest first | conversations:read |
GET /api/v1/conversations/ID, with its messages | conversations:read |
POST /api/v1/conversations/ID/reply { text }: replies as the account owner and takes the conversation over | conversations:reply |
POST /api/v1/conversations/ID/hand-back, POST /api/v1/conversations/ID/close | conversations:reply |
MCP server
Deskee is a Model Context Protocol server at https://deskee.io/mcp (Streamable HTTP), so an AI assistant can build and run a chatbot for you: "Set up a Deskee chatbot for my bakery from my website and match my colours." It uses the same API keys; each tool checks its key's permissions.
Tools: list_chatbots, get_chatbot, create_chatbot, update_chatbot, update_persona, list_knowledge, get_knowledge, add_knowledge, update_knowledge, remove_knowledge, update_website, match_website_theme, get_preview_link, ask_chatbot, list_conversations, get_conversation, reply_to_conversation, hand_back_conversation, close_conversation. Apart from removing a knowledge source, nothing can be deleted. ask_chatbot runs a private test chat that doesn't use the monthly AI conversations.
Claude Code
claude mcp add --transport http deskee https://deskee.io/mcp --header "Authorization: Bearer dsk_…"
Cursor and other clients that take a URL and headers
{
"mcpServers": {
"deskee": {
"url": "https://deskee.io/mcp",
"headers": { "Authorization": "Bearer dsk_…" }
}
}
}
Claude Desktop
{
"mcpServers": {
"deskee": {
"command": "npx",
"args": ["mcp-remote", "https://deskee.io/mcp", "--header", "Authorization: Bearer dsk_…"]
}
}
}
The web connectors on claude.ai and ChatGPT sign in with OAuth rather than a key, so they can't connect yet. We're working on it.
Calendars
Read open times and book appointments in an account's Deskee calendars, with the calendars:read and calendars:book permissions. Bookings get the same confirmation emails and calendar invites as any other.
GET /api/calendars/CALENDAR_ID/slots?from=2026-10-01&to=2026-10-07
POST /api/calendars/CALENDAR_ID/bookings (send an Idempotency-Key header)
{ "start": "2026-10-06T09:00:00+01:00", "attendee_name": "Ada Lovelace", "attendee_email": "ada@example.com" }
POST /api/calendars/CALENDAR_ID/bookings/BOOKING_ID/cancel
Errors and limits
Errors come back as { "error": "…" } with a readable message:
| Status | Means |
|---|---|
400 | Something in the request isn't valid |
401 | The key is missing, wrong or revoked |
403 | The key lacks the permission (named in scope) |
404 | No such chatbot or conversation on this account |
409 | Switched off or closed: the chatbot's API channel or the chatbot is in Draft, the conversation is closed, or a plan limit is reached |
429 | Too many requests: 120 a minute per key (see Retry-After), or the daily limit on whole-website reads (5) or page refreshes (50) |
Questions or ideas? hello@deskee.io