SupportBotHTTP API + drop-in chat widget. Same KB, same actions, same case-creation as the messengers — exposed for any code that can speak HTTPS.
Every request to the API carries one bearer token in the Authorization header. The token is issued per group from Dashboard → API → Reveal. Rotating it from the same panel invalidates the previous value immediately — there is no grace period, so update your deploys before clicking Rotate.
Authorization: Bearer sb_a4f93b27c2e1d508f12c66e9b0a7d4c5
Treat the key like a password. Server-to-server calls can use it directly. Browser code embeds it via the chat widget snippet, which scopes it to the host origin you configured in the dashboard.
The single entry point. Accepts a chat history + the latest user message and returns the bot reply, plus an optional case_id when the conversation produced a case worth keeping.
POSThttps://api.supportbot.info/v1/chat
curl https://api.supportbot.info/v1/chat \
-H "Authorization: Bearer sb_a4f9..." \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "How do I reset my 2FA?"}
],
"session_id": "user-7421",
"end_user_id": "acme_user_7421",
"private": false
}'| Field | Type | Required | Description |
|---|---|---|---|
| messages | array | YES | OpenAI-shaped chat turns. Each item has role (user/assistant) and content (string or content blocks). |
| session_id | string | recommended | Stable id for the conversation thread. Without it, every call is single-turn. |
| end_user_id | string | recommended | Stable id for the person. Used for attribution, per-user rate limits, and the case "from" field. |
| private | bool | — | Default false. false = the conversation is real customer support — case may be created, KB write-back enabled, admins see it. true = ephemeral query; nothing persists. Use true for self-improving agent loops. |
| metadata | object | — | Free-form passthrough. Forwarded into action calls and audit logs. |
Each message.content can also be an array of blocks. Image inputs are passed through to the model and persisted in the case if one is created.
{
"role": "user",
"content": [
{ "type": "text", "text": "Why does my widget look broken?" },
{ "type": "image_url", "image_url": { "url": "https://acme.com/screenshot.png" } }
]
}For browser-uploaded files use POST /v1/files first to get a hosted URL, then reference it in the content block.
{
"id": "msg_01HZA9F2...",
"reply": "Settings → Security → Reset 2FA. We'll email a recovery link valid for 30 min.",
"escalated": false,
"case_id": "case_01HZA9F2YJK...",
"sources": [
{ "title": "FAQ — Account", "url": "https://acme.com/docs/account" },
{ "title": "Case · 2FA reset", "url": "https://supportbot.info/case/abc..." }
],
"tool_calls": [
{ "name": "lookup_user", "args": { "id": "7421" }, "result": "..." }
],
"attachments": [],
"usage": { "input_tokens": 1820, "output_tokens": 184 }
}case_id is null unless the bot decided this exchange was worth persisting.tool_calls reflects any custom Actions configured on the group (Dashboard → Actions).escalated: true means the bot decided a human should weigh in — see Human handoff.
When the bot is unsure or the user explicitly asks for a person, the response comes back withescalated: true and a reply that says something like "I've flagged this for a human — they'll reply here as soon as they're available." The session also lights up in Dashboard → Inbox with a red badge so an admin can pick it up.
The Inbox shows every escalated 1-1 conversation across channels (widget, API, Telegram DM, Signal DM, WhatsApp DM) for groups the admin owns. They can read the thread and reply directly from the dashboard — no channel-switching.
An admin reply is delivered through the same channel the user wrote on, prefixed so they know it's a person:
"Human support: <text>""Admin: <text>" (or the UA equivalent Адмін:)For the API, the admin reply arrives as the next assistant message in the conversation — poll GET /v1/sessions/{session_id}/messages?after=<id> or open a stream. The first admin reply un-escalates the thread, so the badge drops back by one.
Pass private: true if your call is part of an internal agent loop — the bot still answers, but escalation, case creation, and Inbox surfacing are skipped.
One call, one URL, brand tokens out — accent color, font family, border radius, and a greeting line written in the voice of your site. Used by the dashboard's "Generate from your site" button; available to your own tools too.
POSThttps://api.supportbot.info/v1/auto-brand
curl https://api.supportbot.info/v1/auto-brand \
-H "Authorization: Bearer sb_a4f9..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://acme.com" }'
# 200 OK
{
"accent": "#0F62FE",
"font": "Inter, system-ui, sans-serif",
"radius": 12,
"greeting": "Hi! Ask anything about Acme."
}Soft-capped at 3 generations / day per group. On overflow returns429 with a Retry-After header (seconds until reset). Page fetches are bounded to 8 s and 256 KB — pages behind auth or geo-walls return400 fetch_failed with a hint.
For higher-fidelity styling — full chat-widget CSS that matches your brand's typography, density, and shadow recipe — call the streaming endpoint. It runs a multi-agent multimodal loop (probe → extract → code → critic → diff-code) and emits one Server-Sent Event per phase so a UI can show live progress.
POSThttps://api.supportbot.info/v1/auto-brand/stream
curl -N https://api.supportbot.info/v1/auto-brand/stream \
-H "Authorization: Bearer sb_a4f9..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://acme.com" }'
# Server-sent events; each line is "data: <json>".
data: {"phase": "probing", "url": "https://acme.com"}
data: {"phase": "extracting","url": "https://acme.com/", "title": "Acme"}
data: {"phase": "coding", "iter": 1}
data: {"phase": "iter_done", "iter": 1, "accent":"#0F62FE", "font":"Inter",
"radius":12, "custom_css":"...", "greeting":"..."}
data: {"phase": "critic", "iter": 1}
data: {"phase": "coding", "iter": 2}
...
data: {"phase": "done", "iter": 4, "final_score": 9, "elapsed_s": 430.5,
"accent":"#0F62FE", "font":"Inter", "custom_css":"...",
"greeting":"...", "site_name":"Acme"}The final done event also persists the brand into your group, so any subsequent widget-mount picks it up via /v1/widget-config. Shares the same 3 / day soft cap; runs ~6–8 minutes; uses an internal headless browser (not a 256 KB HTML fetch), so it works on JS-heavy sites and pages with brand fonts loaded via @font-face.
All errors return a JSON body with error.code and error.message.
401 Missing or revoked API key.403 Origin not in the public-key allowlist (browser keys only).404 Unknown session_id for an explicit get/replay endpoint.413 Request exceeded 1 MB or message history exceeded 200 turns.429 Rate limit hit. Retry-After header indicates seconds.5xx Transient — safe to retry with exponential backoff.One-line embed for any web page. The snippet is generated for you in Dashboard → API → Chat widget with your key, accent color, and greeting baked in.
<script src="https://supportbot.info/widget.js" data-token="sb_a4f93b27c2e1d508f12c66e9b0a7d4c5" data-accent="#2C6BED" data-position="bottom-right" data-greeting="Hi! Ask anything about Acme." defer></script>
The widget renders an isolated iframe (no host CSS leakage) with a floating bubble that expands into a panel.
| Attribute | Type | Default | Description |
|---|---|---|---|
| data-token | string | — | Your public API key (required). |
| data-accent | color | #2C6BED | Primary color for the bubble + user message. |
| data-position | enum | bottom-right | One of bottom-right, bottom-left. |
| data-greeting | string | — | First bot message shown when the panel opens. |
| data-radius | number | 14 | Border radius (px) for bubbles and panel. |
| data-font | string | system | Override the in-widget font family. |
| data-end-user-id | string | — | If your site has logged-in users, pass their id so threads survive across visits. |
If your app already speaks React, skip the widget and call the API directly so you control the UI:
import { useState } from 'react';
export function Chat({ apiKey, userId }: { apiKey: string; userId: string }) {
const [history, setHistory] = useState<{role:string; content:string}[]>([]);
const [busy, setBusy] = useState(false);
async function send(text: string) {
const next = [...history, { role: 'user', content: text }];
setHistory(next);
setBusy(true);
const r = await fetch('https://api.supportbot.info/v1/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: next,
session_id: userId,
end_user_id: userId,
}),
});
const { reply } = await r.json();
setHistory([...next, { role: 'assistant', content: reply }]);
setBusy(false);
}
// …render history + input box…
}/v1/auto-brand and /v1/auto-brand/stream: 3 generations / day per group (shared quota). Re-runs reuse the cached result unless explicitly busted./v1/files: 10 MB / file, 100 files / hour.429 with a Retry-After header — back off and retry.Need higher limits for production traffic? Email hello@supportbot.info with your expected volume.