API Docs

External bots

AdminUpdated Sep 19, 2026

External bots

Plug Dialogflow, Rasa or your own bot into Chatly — mint a credential, poll for conversations, reply. What the bot surface can and cannot do, stated plainly.

Chatly does not host your bot. It gives your bot a credential, four endpoints, and one workspace to work in. Everything else — the NLU, the dialogue state, the decision to answer or stay quiet — lives in your process, wherever you already run it.

This page is the whole recipe. It is short on purpose: the bot surface is four routes, and that is not an omission from the documentation.

Note — A credential, not a bot object

There is no bot resource in Chatly — no bot profile, no bot directory, no per-bot settings page. What you create is a credential. Read What a bot cannot do before you design around this; the limits are real and they are not on a roadmap you can wait for.

1. Mint a credential

In the dashboard: Developer → Bot credentials → New credential. Or over the API, with a session or an API key that carries workspace:write:

POST /v1/bot-credentials — Bearer token

{
  "name": "Dialogflow triage bot",
  "expiresAt": "2027-01-01T00:00:00.000Z"
}

expiresAt is optional; omit it and the credential does not expire. The response carries the token exactly once:

{
  "id": "01H7...",
  "workspaceId": "01H7...",
  "name": "Dialogflow triage bot",
  "prefix": "cb_live_9fA",
  "createdByPlatformAppId": null,
  "createdAt": "2026-08-27T09:12:44.000Z",
  "lastUsedAt": null,
  "expiresAt": "2027-01-01T00:00:00.000Z",
  "revokedAt": null,
  "token": "cb_live_...",
  "note": "Copy this now — it is hashed on save and cannot be shown again."
}

Warning — Save the token now

Only a SHA-256 of it is stored. GET /v1/bot-credentials returns the list without tokens, and there is no rotate endpoint. If you lose it, revoke the credential and mint another.

The other two routes are what you would expect. Revoked credentials stay in the list — "was that credential ever actually turned off" is the question you ask after a leak, and a disappearing row cannot answer it.

GET    /v1/bot-credentials
POST   /v1/bot-credentials
DELETE /v1/bot-credentials/{id}

createdByPlatformAppId tells you where a credential came from: null means someone in your workspace minted it, and a value means a partner did. Both appear in your list, because the question that list answers is "what can reach our conversations".

2. Check what you are holding

Every subsequent call presents the token as a bearer credential. Start here — it turns a mistake now into an obvious one, rather than a 401 an hour into an integration.

GET /v1/bot/whoami — Bearer token

curl https://api.chatlychat.com/v1/bot/whoami \
  -H "Authorization: Bearer $CHATLY_BOT_TOKEN"
{
  "botId": "01H7...",
  "name": "Dialogflow triage bot",
  "workspaceId": "01H7...",
  "capabilities": ["conversations:list", "conversations:read", "conversations:reply"]
}

The workspace is taken from the credential's own record. There is no workspace parameter anywhere on this surface — a bot cannot ask about a tenant it was not issued for, and there is no header that changes that.

3. Poll for work

GET /v1/bot/conversations — Bearer token

Returns up to 50 open conversations, most recently active first. Closed and merged threads are excluded.

[
  {
    "id": "01H7...",
    "status": "open",
    "priority": "normal",
    "subject": "Where is my order?",
    "channelType": "web",
    "lastMessageAt": "2026-08-27T09:14:02.000Z",
    "lastMessagePreview": "hi, order 5512 hasn't arrived",
    "assigned": true
  }
]

assigned is a boolean, not a person. A bot needs to know whether a human has already picked a thread up so it can stay out of the way; it does not need to know which member of staff that is.

Tip — Polling, not push

This is a polling surface. Chatly does not call your bot — there is no outgoing URL on a bot credential and no bot-specific callback. If you want Chatly to push to you, that is a different feature: webhooks, which are workspace-scoped and authenticated differently. Many teams run both: a webhook to wake the bot, the bot surface to read and reply.

A sensible loop polls every few seconds, keeps the ids it has already answered, and backs off when the list is empty.

4. Read the thread

GET /v1/bot/conversations/{id}/messages — Bearer token

{
  "conversation": { "id": "01H7...", "status": "open", "subject": "Where is my order?" },
  "messages": [
    {
      "id": "01H7...",
      "authorType": "contact",
      "kind": "text",
      "body": "hi, order 5512 hasn't arrived",
      "createdAt": "2026-08-27T09:14:02.000Z"
    }
  ]
}

Up to 100 messages, oldest first. Internal notes are filtered out. Your bot sees what the customer sees, not what the team said about the customer.

5. Reply

POST /v1/bot/conversations/{id}/messages — Bearer token

{
  "body": "Order 5512 shipped yesterday — here is the tracking link.",
  "kind": "text"
}

kind is text or note, and it defaults to text.

  • text reaches the customer, on whatever channel the conversation is on.

  • note writes an internal note only the team sees. This is the polite option when the bot is unsure: leave what it found, let a human send it.

The message is stamped bot-authored in the transcript. That is deliberate — a bot's reply being indistinguishable from a human's would be the actual problem here.

Rate limit: 60 replies per minute per credential.

What a bot cannot do

Read this before you plan around the feature. These are structural limits, not gaps waiting on a release.

A bot cannot be an assignee. There is no assigneeBotId, and assignment in Chatly points at a user with a seat. You cannot route a conversation to a bot, you cannot see a bot in an assignee filter, and a bot cannot take or release a thread. What a bot can do is notice that assigned is false and answer anyway.

A bot cannot be bound to an inbox. There is no mapping from a channel or an inbox to a bot, so a bot is not "the bot for the web widget". It sees every open conversation in the workspace and decides for itself what to act on. Filtering by channelType is your side's job.

A bot cannot change a conversation's state. No assigning, no closing, no snoozing, no priority, no tags, no labels. It reads and it writes messages.

A bot cannot reach anything else in the workspace. Not contacts, not the knowledge base, not search, not team members, not billing, not settings, not reports. Those endpoints exist and are documented, but a cb_ credential is refused on all of them — it carries no scopes and its authority is the four routes above.

A bot cannot read internal notes, though it may write one.

If you need any of that, use an API key instead. A key is scoped, can be granted conversations:read and messages:write, and reaches the full REST API — it simply is not a bot, and messages it sends are not stamped as bot-authored.

Keeping it safe

A bot credential is a standing machine credential with no expiry unless you set one. Treat it like a password:

  • One credential per bot per environment. Sharing one between staging and production means revoking it takes both down.

  • Set expiresAt when the integration is for a fixed engagement.

  • Revoke from the dashboard the moment a bot is retired. Revocation takes effect on the next request; there is no cache to wait out.

  • Every mint and every revoke is written to your workspace audit log, with the person who did it.

Was this page helpful?