API reference
REST API v1 and outgoing webhooks · Last updated: October 14, 2026
Authentication
Create a secret API key in your dashboard under Security → API keys, and give it only the scopes it needs. Send it on every request:
Authorization: Bearer sk_live_…
Base URL: https://chatbotreply.com/api/v1. Keep secret keys on your server. The public widget key (cb_…) is embedded in websites and cannot use this API.
A key from another region gets 421 with the API address to call instead.
Scopes
| Scope | Allows |
|---|---|
conversations:read | List conversations and read their messages |
conversations:write | Reply in a conversation as a person; send customer messages from your own channel |
tickets:read / tickets:write | Read / create and update tickets |
knowledge:read / knowledge:write | Read FAQs / add FAQs and documents |
leads:read | Read captured leads |
analytics:read | Read metrics and agent versions |
orders:write | Send your orders, so the agent can answer order questions |
Limits and retries
- Rate limit: 120 requests per minute per key and IP address. Above that you get
429; wait a minute and retry. - Safe retries: send an
Idempotency-Keyheader (any unique string, up to 100 characters) onPOSTrequests. A retry with the same key returns the stored response instead of doing the work twice. While the first request is still running, a retry gets409.
Endpoints
All bodies and responses are JSON. Conversations created by your own test runs (QA Lab) are left out of lists.
| Request | Scope | What it does |
|---|---|---|
GET /conversations?limit=50 | conversations:read | Latest conversations (limit 1–200) |
GET /conversations/{id} | conversations:read | One conversation, with its handoff card if a person was asked for |
GET /conversations/{id}/messages?limit=50 | conversations:read | Its messages (limit 1–200) |
POST /conversations/{id}/messages | conversations:write | Reply as a person: { "text": "…" } (up to 2,000 characters). Sent on the customer's channel (WhatsApp, Messenger…) or shown in the website widget; the bot stays quiet in that conversation. Returns 201 { ok, message_id, delivered_via } |
POST /messages | conversations:write | Your own channel: send a customer's message and get the agent's reply. Body { "text": "…", "session_id"?: "…", "visitor_label"?: "…" } (text up to 4,000 characters). Returns { sessionId, reply, type, confidence, suggestions, sources, … }; pass sessionId back as session_id to continue the conversation |
GET /tickets?status=open | tickets:read | Tickets, optionally by status |
POST /tickets | tickets:write | Create: { "subject": "…", "session_id"?: "…", "priority"?: "low" | "normal" | "high" | "urgent" } → 201 |
PATCH /tickets/{id} | tickets:write | Update any of status (open, pending, resolved, closed), priority, assignee, subject |
GET /knowledge/faqs | knowledge:read | All FAQs |
POST /knowledge/faqs | knowledge:write | Add: { "question": "…", "answer": "…", "keywords"?: "…" } → 201 |
POST /knowledge/documents | knowledge:write | Add or update a document the agent can answer from: { "title": "…", "text": "…", "url"?: "https://…" } → 201 { ok, source, stored, changed }. The same URL (or title) replaces the earlier version |
GET /leads | leads:read | Captured leads |
GET /analytics?days=30 | analytics:read | The dashboard's metrics for the last 1–365 days |
GET /agents | analytics:read | Agent versions: id, version, status, rollout and publish time |
POST /orders | orders:write | Add or update up to 500 orders: { "orders": [{ "order_number": "…", "status": "…", "eta"?, "carrier"?, "tracking_number"?, "items"?, "customer_email"?, "customer_phone"?, "total"?, "currency"? }] } → { ok, upserted }. Orders without a number or status are skipped |
Example
curl https://chatbotreply.com/api/v1/messages \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e" \
-d '{"text":"Do you ship to Canada?","visitor_label":"in-app chat"}'
Errors
Errors are JSON: { "error": "what went wrong" }.
400: missing or invalid fields.401: missing, invalid or revoked key, or the public widget key.403: the key lacks the scope.404: not found, or not your business's.409: the sameIdempotency-Keyis still in progress.421: wrong region.429: rate limit.
Outgoing webhooks
Add endpoints in your dashboard under Integrations → Outgoing webhooks, and choose which events each one gets (none chosen = all). The dashboard shows each endpoint's latest result, a delivery log, and a Send test event button.
- Addresses: endpoint URLs must be public
https://addresses on the standard port (443); private and internal addresses are refused. Adding endpoints and sending test events are limited to 20 a minute. - Signing secret: shown once, when you add the endpoint.
Request
Each event is a POST with a JSON body:
{ "id": "3b1f…", "event": "lead.created", "business_id": "biz_…", "occurred_at": "2026-10-14T12:00:00.000Z", "data": { … } }
| Header | Value |
|---|---|
X-Webhook-Id | The delivery id (also id in the body). It is the same on every retry: use it to ignore duplicates |
X-Webhook-Event | The event name |
X-Webhook-Timestamp | When it was sent, in milliseconds since 1970 |
X-Webhook-Signature | Hex HMAC-SHA256 of timestamp + "." + raw body, keyed with the signing secret |
Check the signature (Node.js)
const crypto = require('crypto');
function isFromChatbotReply(secret, headers, rawBody) {
const ts = headers['x-webhook-timestamp'];
if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) return false; // too old: possible replay
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
const got = String(headers['x-webhook-signature'] || '');
return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}
Check against the raw request body, before parsing it as JSON.
Retries
Answer with any 2xx status within 10 seconds. Anything else, a timeout or a network error is retried. There are up to 5 attempts, after waits of 30 seconds, 1, 2 and 4 minutes, so about 8 minutes in all. Every attempt shows in the dashboard's delivery log.
Events
- Conversations:
conversation.started,conversation.resolved,conversation.escalated. - Messages:
message.received,message.sent. - Handoffs and tickets:
handoff.requested,ticket.created,ticket.updated. - Leads:
lead.captured,lead.created. - Actions:
action.executed,action.started,action.completed,action.failed,action.approval_requested. - Knowledge and agent:
knowledge.updated,agent.deployed. - Test: the dashboard's test button sends
webhook.test.