Build on aichat.md — conversational AI agents, on any channel.
One platform, one integration. Put a smart agent on your website with a single line of code, connect Facebook, Instagram, Telegram and WhatsApp, or control everything programmatically through the REST API: agents, tools, conversations, leads, CRM, automations, knowledge and analytics.
Quick start
The agent on your website, in three steps:
- Create an agent in the aichat.md dashboard and copy its widgetId (a UUID, from Agent → Channels → Chat Widget).
- Paste the embed script before
</body>. - Or, for your own UI, call
POST /widget/chat.
<script src="https://aichat.md/api/v1/widget/bridge/chatbot-script.js?chatflow_id=WIDGET_ID" defer></script>For programmatic access to agents, channels, conversations or CRM, first obtain a token — see Authentication.
Base URL & versions
| Base URL | https://aichat.md/api/v1 |
| Format | JSON on request and response; Content-Type: application/json headers. |
| Widget | Public routes under /widget, identified by widgetId. Open CORS. |
| Management | Routes authenticated with Authorization: Bearer <token>. |
| Versions | Almost everything is under /api/v1. Only one area also has /api/v2: the dashboard (/api/v1/dashboard and /api/v2/dashboard are identical; use v2). |
Authentication
There are two levels of access:
Public — widget
The /widget/* routes are public, identified by widgetId. No key in the front end. Access is restricted through the agent's list of allowed origins.
Authenticated — account
The rest of the API requires a Bearer JWT obtained through login. The same token is accepted both as a header and as the access_token cookie.
Programmatic authentication is currently done with a session JWT (login → token). There is no long-lived workspace API key to put in .env (yet) — for server-to-server integrations, reuse the login token and renew it with refresh.
Getting a token
Flow: register → verify-email → login. Endpoints that issue a token return { accessToken, refreshToken }.
curl -X POST https://aichat.md/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "tu@firma.md", "pass": "••••••••" }'
# → 200 { "accessToken": "eyJ…", "refreshToken": "eyJ…" }
# → 202 { "twofa_required": true, "pendingToken": "…" } if 2FA is enabled
# → 228 { "email": "…" } email not confirmed (a NEW code is resent automatically)Watch the field names — they are not uniform: register requires pass, while login accepts pass or password; verify-email uses verificationCode (not code); only 2FA and reset use code.
Authentication endpoints
| Method & route | Body | Result |
|---|---|---|
POST /auth/register | { email, pass } | 200 { email } — then email verification. |
POST /auth/verify-email | { email, verificationCode } | { accessToken, refreshToken } |
POST /auth/regenerate-code | { email } | Resends the verification code. |
POST /auth/login | { email, pass } | Tokens · 202 2FA · 228 email not confirmed (the server automatically resends a new code → go straight to verify-email). |
POST /auth/2fa/login-verify | { pendingToken, code, remember_device? } | { accessToken, refreshToken } |
POST /auth/refresh | { refreshToken } (or cookie) | Rotates and returns new tokens. |
POST /auth/reset-password | { email } | Sends a reset code. |
POST /auth/new-password | { email, code, newPassword } | { accessToken, refreshToken } (auto-login). |
POST /auth/pass-change 🔒 | { password, newPassword } | Changes the password; invalidates all sessions. |
POST /auth/revokeRefreshTokens 🔒 | {} | Global logout — revokes all of your own refresh tokens (only your account's). |
2FA (TOTP) — management (all 🔒 Bearer)
POST /auth/2fa/setup (→ { secret, qrCode }) · /2fa/verify-enable (→ { backupCodes }) · /2fa/disable · /2fa/status · /2fa/backup-codes/regenerate · GET /2fa/trusted-devices · DELETE /2fa/trusted-devices/:id (the device id from the list).
Then send the token on any authenticated route: Authorization: Bearer <accessToken>. Auth error: 401 { message: "🚫 Un-Authorized 🚫" }.
Conventions & pagination
Response shape
The convention is not uniform across the platform — check it per area:
| Shape | Areas |
|---|---|
{ success: true, data: … } | widget, conversations, handoff, usage, automation-engine, keywords, dashboard, knowledge, template-library |
"bare" object/array (no success) | agents, leads, amoCRM (reads), sequences, broadcasts, workflows, analytics, notes, tags, canned-responses |
Pagination
Large list endpoints accept page + limit (e.g. /conversations, /leads) or cursor + limit (e.g. /conversations/:id/messages). Where no cap is specified, limit has a safety maximum (e.g. usage/transactions ≤ 500).
Time zones, encryption, ids
Date-times are in the Chișinău time zone (Europe/Chisinau) unless specified otherwise. Agents and the widget use UUIDs; conversations/threads use numeric ids.
Errors & rate limits
Response codes
| Code | Meaning |
|---|---|
200 / 201 | OK. |
202 | Accepted — asynchronous process (e.g. file indexing) or 2FA required. |
228 | Email not confirmed (at login) — not an error; send the user to the verification screen. |
400 | Invalid request (missing field / wrong format). |
401 / 403 | Unauthenticated / forbidden (missing token, unauthorized origin, disabled agent). |
402 | credits_exhausted. |
404 | Resource / widget does not exist. |
409 | Conflict (e.g. duplicate lead within 24h, number already connected). |
422 | Unprocessable entity (comment moderation rejected, WhatsApp without numbers). |
429 | Too many requests. |
430 | Outside the Meta 24h window (Messenger/IG) — you cannot send a free-form message. |
500/502/504 | Server error / service temporarily unavailable — retry with backoff. |
The error contract differs between layers. The widget layer returns { success:false, error } + X-RateLimit-* headers + retryInSec. The management layer (authenticated) has a global limiter of 1000 requests / 15 min / IP that returns 429 as plain text with RateLimit-* headers (no JSON). Handle the two differently.
Rate limits — widget
| Scope | Limit |
|---|---|
| Chat / per widget | 600 / min |
| Chat / per IP | 120 / min |
| Config / per widget · per IP | 1200 / min · 90 / min |
| Order / per IP | 5 / min |
| History / per IP | 60 / min |
Widget installation
Three embed methods. Pick one:
1. Bridge script — recommended (web component)
<script src="https://aichat.md/api/v1/widget/bridge/chatbot-script.js?chatflow_id=WIDGET_ID" defer></script>2. Loader — safe for Google Tag Manager (web component)
<script async src="https://aichat.md/api/v1/widget/loader.js?widget-id=WIDGET_ID"></script>3. Static embed v2 — iframe
<script src="https://aichat.md/api/v1/widget/v2/embed.js?id=WIDGET_ID"
data-position="right" data-color="#0d9c93" data-lang="ro"></script>Methods 1 and 2 mount the <aichat-chat> web component; method 3 is an iframe (launcher button + <iframe>) and accepts the data-id/data-position/data-color/data-lang attributes. CSS selectors on aichat-chat do NOT work with method 3.
The look (color, name, avatar, message) is set from the dashboard or through the widget config API. The snippet can also be generated programmatically with POST /widget/embed-code { chatbot_id } → { success, html }.
Conversation — chat
| Field | Description | |
|---|---|---|
widgetId | required | The agent's UUID (also accepts widget_id). |
messages | required* | [{ role, content }], role = user/assistant. |
message | alt. | A single message, instead of messages. |
sessionId | recommended | Ties messages into a conversation. See the note below. |
attachments | optional | Images (max 3, ≤4 MB, jpeg/png/webp/gif). |
sessionId is NOT returned in the response. If you don't send it, consecutive anonymous requests do not share a conversation (no memory). Generate and persist a sessionId YOURSELF (e.g. in localStorage) and send it with every message — it is also the key for the history.
curl -X POST https://aichat.md/api/v1/widget/chat \
-H "Content-Type: application/json" \
-d '{
"widgetId": "1d00a200-8ad9-4bb3-a165-7cfa45960e1d",
"sessionId": "sess-42",
"messages": [{ "role": "user", "content": "What services do you offer?" }]
}'
const sessionId = localStorage.getItem("aichat_sid")
?? (localStorage.setItem("aichat_sid", crypto.randomUUID()), localStorage.getItem("aichat_sid"));
const res = await fetch("https://aichat.md/api/v1/widget/chat", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ widgetId: "WIDGET_ID", sessionId, messages: [{ role: "user", content: text }] })
});
const data = await res.json();
console.log(data.message, data.products);Response (200)
{
"success": true,
"message": "We offer manicure, pedicure and podiatry treatments…",
"products": [{ "id","title","description","price","currency"?,"image_url"?,"product_url"?,"sku"?,"availability"? }],
"sources": [{ "index","title","snippet","url"? }],
"citations": [ … ], "media": [{ "type","url","title" }],
"actions": [{ "label","url","type" }],
"quick_replies": [{ "label","value" }],
"metadata": { "schema": "aichat.rich.v1", "quickReplies", "products", "sources", … } // camelCase duplicate
}Streaming — token by token (SSE)
The response is Server-Sent Events: each event is a data: <JSON> line, with its type in the type field. Body: { widgetId, messages | message, sessionId?, images? }.
| Event | Payload |
|---|---|
token | { content } — a chunk of text (repeated). |
tool_start | { message } — the agent is running a tool. |
metadata | { products, quick_replies, sources, citations }. |
done | { duration_ms, tokens_in, tokens_out, tools_used, … }. |
error | { message } — sent on the stream (HTTP stays 200). |
On the stream, sources and citations are always empty (reserved), and products have a different shape than on /chat: { title, description, price, image, url, sku, in_stock } — image/url (not image_url/product_url). Don't blindly reuse the /chat renderer.
const res = await fetch("https://aichat.md/api/v1/widget/v2/chat/stream", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ widgetId: "WIDGET_ID", sessionId, message: text })
});
const reader = res.body.getReader(), dec = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
const parts = buf.split("\n\n");
buf = parts.pop(); // keep the incomplete fragment
for (const block of parts) {
if (!block.startsWith("data:")) continue;
const ev = JSON.parse(block.slice(5));
if (ev.type === "token") ui.append(ev.content);
}
}Widget config — public read
The agent's public configuration (name, message, color, avatar, suggestions, products). The canonical endpoint is POST; the GET variant is an alias with ETag/304 caching. For setting the config (owner, with a token) see Agent widget config.
POST requires the body { widget_id }; GET takes widget_id from the path and adds ETag + Cache-Control: max-age=60 (send If-None-Match to get 304). Response: { success, config: { name, primary_color, welcome_message, placeholder, assistant_name, greeting_message, position, starter_prompts[], agent:{name,avatar,tone}, products[], cart_enabled } }.
Conversation history
The session's last 40 messages: { success, messages: [{ role, content, time }] }. Both parameters are required.
Order
| Field | Description | |
|---|---|---|
widget_config_id | required | The widget identifier (the same UUID as widgetId). Without it → 400. |
customer_name | required | The customer's name. |
cart_items | required | The products (non-empty list). |
total_amount | required | Total (> 0, ≤ 1 000 000). |
customer_phone | optional | 8–15 digits. |
customer_address · customer_notes | optional | Delivery & notes. |
Success: { success: true, order_id }. Rate limit 5/min/IP; 24h deduplication on phone+widget (409 duplicate_lead_24h).
{
"widget_config_id": "WIDGET_ID",
"customer_name": "Ana Pop",
"customer_phone": "37360123456",
"customer_address": "33 Ismail St, Chișinău",
"cart_items": [
{ "id": "sku-12", "title": "Set California", "price": 180, "qty": 2 }
],
"total_amount": 360,
"customer_notes": "no wasabi"
}Headless integration — the agent in your UI
Already have a chat on your website and only want the "brain"? You can integrate the full agent into your own interface — without our bundle, without an SDK, without cookies. The whole surface is a public JSON/SSE API, authenticated only by widgetId. In practice you call the same endpoints described above from your own code.
curl -sS https://aichat.md/api/v1/widget/chat \
-H 'Content-Type: application/json' \
-d '{
"widgetId": "WIDGET_ID",
"sessionId": "user-42-conv-7",
"messages": [{ "role": "user", "content": "Hi, do you deliver to Chișinău?" }]
}'
# → { success, message, products, quick_replies, sources, actions, ... }The pieces you need, all documented above:
| Purpose | Endpoint |
|---|---|
| Full response | POST /widget/chat |
| Real-time typing | POST /widget/v2/chat/stream (SSE) |
| Persona for your UI (name, avatar, suggestions) | GET /widget/{id}/config |
| Conversation rehydration | GET /widget/history |
Authentication & CORS
- Auth =
widgetIdonly. No API key, no Bearer, no cookie, no identity HMAC. - CORS on
/widgetreflects any origin (credentials: false) — your domain is accepted by default. - The agent's allowed-origins list is checked at application level and is bypassed when there is no
Originheader (server-side call). So if you call from your backend, you are never blocked. If you call directly from the browser, ask the agent's owner to add your domain to the allowed origins.
Security recommendation: call from your backend (you keep widgetId hidden) and put the verified user's identity in sessionId (e.g. clientUserId:conversationId). There is no cryptographic user↔session binding of the "user_hash" kind (yet).
Memory / session
You own the sessionId (a stable string per user-conversation). The server persists every turn, but does not automatically re-inject the history into the prompt — for multi-turn, send the history yourself in messages[] (max 20 messages, 4000 characters/message, 3 images).
Recommended vs legacy
| Use | Avoid (old Flowise compat) |
|---|---|
POST /widget/chat | POST /prediction/:chatflowId |
POST /widget/v2/chat/stream | GET /chatflows/:id, /chatflows-streaming/:id |
GET /widget/:id/config | /public-chatbotConfig/:id, /runtime-config/:id |
GET /widget/history | /chatmessage/:id, /apif/* |
Agents
Create and manage agents programmatically. All routes require Bearer. An agent has: a name, instructions (system prompt), model, AI provider, tone, channels and config.
| Route | Description |
|---|---|
GET /agents | The account's agents. |
POST /agents | Creates an agent. Body: name*, instructions, model, aiProvider, description, tone, answerLength, config. |
GET /agents/:id | Details + channels + metadata. |
PUT /agents/:id | Updates (name, instructions, model, temperature, config, capabilities, language…). |
DELETE /agents/:id | Deletes the agent. |
POST /agents/:id/toggle-pause | Pause / resume ({ paused }). |
POST /agents/onboarding/parse-website | Extracts content from a website ({ url }). |
POST /agents/onboarding/generate-instructions | Generates a system prompt. |
POST /agents/onboarding/complete | Creates the agent from the onboarding config. |
temperature is locked at 1.0 on creation (any value sent with POST is ignored), but it can be changed later through PUT /agents/:id.
curl -X POST https://aichat.md/api/v1/agents \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Salon assistant", "instructions": "You are the online assistant…", "model": "gpt-4o-mini", "tone": "friendly" }'Agent widget config
Atomic merge into channels.widget. Fields: theme_color, persona_name, persona_avatar_url, welcome_message, subtitle, placeholder, starter_prompts (max 4, ≤80 chars), position, enabled, pet_name, show_online_dot, theme_mode, quick_replies_enabled, auto_focus_input, allowed_origins, plus hide_branding and footer_text (PRO+ only). Unified alternative: PUT /agents/:id/widget-config (also accepts legacy camelCase keys).
Models & providers
An agent has a model (a text name, e.g. gpt-4o-mini) and an ai_provider (default azure). The available values depend on the account's plan — get the actual list with:
Response: { models, userPlanId }. Use a model from this list with POST /agents / PUT /agents/:id. (There is also the historical alias /asisstants/models, with a double "s".)
"agent" = "assistant". The parameter name differs between endpoints: agentId (agents/altegio), assistantId (sheets/knowledge), asistantId (files — a single "s"), assistant_id (chatbots/pause-rules). Check the exact spelling for each endpoint.
Tools (functions)
Give the agent capabilities — call an API, run code or a built-in function. Bound to an agent. All routes require Bearer.
| Route | Description |
|---|---|
GET /agents/:agentId/tools | The list of tools. |
POST /agents/:agentId/tools | Creates. Required: name, description, tool_type (+ conditionally webhook_url / code_body). |
PUT /agents/:agentId/tools/:toolId | Updates. |
DELETE /agents/:agentId/tools/:toolId | Deletes. |
PATCH /agents/:agentId/tools/:toolId/toggle | Enables / disables. |
POST /agents/:agentId/tools/attach | Attaches a tool from the account's library ({ tool_name } or { flowise_tool_id }). |
POST /agents/:agentId/tools/detach | Detaches ({ tool_name }). |
POST /agents/:agentId/tools/:toolId/test | Runs with test arguments. |
For /test, the arguments must be nested under arguments: body { "arguments": { … } }. Top-level → ignored. Note: name is normalized automatically ([^a-zA-Z0-9_]→_, lowercase) — "Verifică Stoc" becomes verific__stoc.
The three types
webhook
Calls a URL. webhook_url, webhook_method (default POST), webhook_headers. {{var}} interpolation from the arguments.
code
Sandboxed JavaScript (code_body; javascript only). Receives args + a minimal fetch (status/ok/headers/json/text, 10s timeout); console disabled, modules blocked (except url), code timeout timeout_ms (default 15s).
builtin
Built-in capability (builtin_type + builtin_config): Google Sheets, Altegio, SMS, Telegram notifications and more.
The parameters field (the arguments seen by the model) is a list:
curl -X POST https://aichat.md/api/v1/agents/AGENT_ID/tools \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "check_stock",
"description": "Checks a product's stock by code",
"tool_type": "webhook",
"webhook_url": "https://api.shop.md/stock?sku={{sku}}",
"webhook_method": "GET",
"parameters": [{ "name": "sku", "type": "string", "required": true }]
}'Conversations
The unified inbox across all channels. All routes require Bearer. Responses { success, data }.
| Route | Description |
|---|---|
GET /conversations | List (query platform, search, tab, page, limit). |
GET /conversations/:id | One conversation + recent messages. |
GET /conversations/:id/messages | Paginated messages (cursor, limit). |
POST /conversations/:id/messages | Sends a human reply ({ content }). 502 if delivery on the channel fails. |
PUT /conversations/:id/tags | Overwrites the tags ({ tags }). |
POST /conversations/:id/read | Marks as read. |
GET /conversations/:id/search | Searches the conversation's messages (?q=). |
GET /conversations/media-proxy?url= | FB/IG media proxy through the origin. |
GET /conversations/:id/refresh-avatar | Refreshes the profile picture (Meta Graph) when the signed CDN URL expires → { success, profile_pic, name }. |
Human handoff
Moves a conversation from the bot to a human operator and back. Bearer, responses { success, data }.
GET /handoff/pending | Conversations waiting for a human. |
GET /handoff/mine | My conversations. |
POST /handoff/:id/take | Takes over the conversation. |
POST /handoff/:id/resolve | Resolves ({ notes? }). |
POST /handoff/:id/return | Hands control back to the bot. |
POST /handoff/:id/stop-bot · /start-bot | Stops / restarts automatic replies. |
POST /handoff/:id/cancel-auto-pause | Cancels an active automatic pause. |
Notes · Tags · Canned · Export · Contacts · Pause-rules
Inbox tools. All Bearer.
| Area | Endpoints |
|---|---|
Notes /notes | GET /:threadId · POST / ({ threadId, content }) · PUT /:id · DELETE /:id (both only by the note's author; otherwise 403). |
Tags /tags, /threads | GET/POST /tags · DELETE /tags/:id · GET /threads/:threadId/tags · POST /threads/:threadId/tags ({ tagId }) · DELETE /threads/:threadId/tags/:tagId. |
Canned responses /canned-responses | GET · POST ({ shortcut, title, content }) · PUT /:id · DELETE /:id. |
Export /export | GET /:threadId?format=json|csv · GET /:threadId/pdf (print-ready HTML). |
Contacts /contacts | GET /duplicates · POST /merge ({ primaryThreadId, secondaryThreadId }). |
Pause rules /pause-rules | GET /defaults · GET/POST /config · GET/POST / (per assistant_id). |
Web push /push | POST /subscribe · /unsubscribe · GET /status · GET /vapid-key (public). |
Bulk inbox /threads-bulk | POST /bulk-action ({ threadIds[], action }; action ∈ close|pause|unpause|mark_spam|export). |
Meta templates
Sends structured templates on Messenger (proxy to Facebook Graph). Bearer; all require recipient_id and return { success, message_id }.
POST /templates/receipt | Receipt template (order_number, summary, items[]…). |
POST /templates/list | List template (elements ≥ 2). |
POST /templates/media | Media template (url or attachment_id). |
POST /templates/otn/request · /otn/send | One-Time Notification: request permission, then send with the token. |
Template library (CRUD) — /templates/library
GET /library · GET /library/most-used · GET /library/variables · POST /library/validate · GET /library/:id (+/preview) · POST /library ({ name, templateType, content }, types: text/quick_replies/buttons/generic/carousel/media) · PUT /library/:id · POST /library/:id/duplicate · DELETE /library/:id.
Facebook Messenger
A page is connected through OAuth from the dashboard; the API binds a page to an agent. All require Bearer.
GET /facebook/oauth-init | The Facebook OAuth dialog URL. |
GET /facebook/pages | The pages + which agent each one has (pages[].assigned.chatbotId). |
POST /facebook/pages/assign | Binds a page to an agent ({ pageId, agentId }) + subscribes the webhook. |
DELETE /facebook/pages/unassign | Detaches ({ chatbotId }, not pageId!). Non-destructive. |
POST /facebook/send-message | Message as an operator ({ receiver_user_id, text, reply_to_mid? }; pauses the bot). 430 outside the Meta window. |
POST /facebook/send-attachment | Attachment (multipart file + receiver_user_id). |
unassign is done by chatbotId (from GET /pages → assigned.chatbotId), NOT by the pageId used for assign. The same rule applies to Instagram.
All require Bearer.
GET /instagram/login · /pages | OAuth URL · IG accounts + assignment. |
POST /instagram/pages/assign | Binds an IG account to an agent ({ instId, agentId }). |
DELETE /instagram/pages/unassign | Detaches ({ chatbotId }). |
POST /instagram/pages/revoke | Deletes the connection locally (requires { instId }; does not revoke the OAuth grant on Meta). |
POST /instagram/unlink | Account-level disconnect. |
POST /instagram/send-message · /send-attachment | Message / attachment as an operator. |
Telegram
Shared bot (@aichat_connect_bot) — the customer does not enter a bot token. Connection: 6-digit code → in the bot → Telegram Business Connection (Premium) → business_connection_id. All require Bearer.
POST /telegram/get-code | Generates the connection code ({ success, code }). |
GET /telegram/business-connected | Status ({ connected, business_connection_id }). |
POST /telegram/unlink · /send-message | Disconnect · message as an operator. |
WhatsApp (Cloud API)
ready connection does not prove that the recipient received a message.Connect in the dashboard
- Create and activate an agent.
- Open WhatsApp and start Embedded Signup if pilot access is available.
- Authorize your own business, WABA and phone number. Do not paste access tokens into the dashboard.
- Complete any verification and registration required by Meta; check the webhook subscription and assigned agent.
- Send an inbound message from a dedicated authorized test phone and confirm the reply on the phone, not only in the inbox.
You need the required rights to the selected assets. A number can belong to only one aichat.md account. Meta test numbers can send only to recipients allowed in their test list.
Authenticated API
Prefix: /api/v1; valid session or Bearer. Onboarding operations remain gated by pilot access.
GET /whatsapp/config | Availability, app, configuration and signed attempt session valid for 10 minutes. |
GET /whatsapp/agents | The account's active agents. |
POST /whatsapp/connect | Requires code, phone_number_id, waba_id, agentId, attempt; pin when registration requires it. The session is consumed once before code exchange. |
POST /whatsapp/complete | Resume incomplete setup with phone_number_id, agentId and pin when required. |
GET /whatsapp/status | Connections, agent, subscription and missing conditions. Historical verification is not a fresh token validation on every request. |
POST /whatsapp/agent | Change the agent on your connection: phone_number_id, agentId. |
POST /whatsapp/disconnect | Disable the local connection: phone_number_id. Revoking Meta access is a separate action. |
GET /whatsapp/numbers/:phoneId/templates | Templates for your own connection. |
GET /whatsapp/events | Events needing attention; a retry may be refused when delivery could be duplicated. |
POST /whatsapp/connect-manual | Retired: 410 use_embedded_signup. There is no pasted-token fallback. |
Messages and operator handoff
Free-form messages are restricted to the 24-hour window from the customer's last message. Outside that window, use an approved template and respect recipient opt-in. Operator takeover does not extend this window. Returning to AI must be intentional. Check agent pause and disconnection before resuming sends.
Meta acceptance, sent, delivered, read and failed are distinct states; read receipts also depend on recipient settings. AI-generated text is not delivery proof. Calling, coexistence and campaigns are not presented here as available features.
Troubleshooting
503 whatsapp_rollout_pending: the account has no pilot access.401 signup_session_expiredor409 signup_session_already_used: restart connection for a fresh session.409 whatsapp_number_already_connected_to_another_account: the asset belongs to another account.phone_verification_required/registration_pin_required: complete the indicated Meta step.- Meta
131030: the recipient is not allowed for this test number. Do not automatically send to a different number. whatsapp_template_required: the free-form messaging window has closed.
Conversations are retained in the inbox. See the privacy policy and data deletion procedure. Disconnecting a channel does not automatically erase history.
Meta webhooks
For FB/IG/WhatsApp, messages come in through the Meta webhook — configured automatically on connection. You don't host anything.
GET /api/v1/webhook public | Meta verification (hub.challenge + verify-token). |
POST /api/v1/webhook public | Receives events (signed with X-Hub-Signature): messages, comments, reactions, statuses. |
Messenger / Instagram profile
Configures a page's messaging profile: "Get Started" button, persistent menu, greeting message, ice-breakers. /messenger-settings, Bearer. :platform = facebook or instagram.
GET /messenger-settings/:platform · PUT /:platform | Reads / saves the profile settings. |
POST /messenger-settings/:platform/get-started | The "Get Started" button. |
POST /messenger-settings/:platform/persistent-menu | Persistent menu. |
POST /messenger-settings/:platform/greeting | Greeting message. |
POST /messenger-settings/:platform/ice-breakers | Ice-breakers (suggested questions on open). |
GET /messenger-settings/:platform/live-profile | The current profile, straight from Meta. |
Automation engine
Event-driven rules (trigger → conditions → actions). Bearer, responses { success, data }.
POST /automation-engine/triggers | Creates a trigger. E.g.: { integrationId, name, triggerType:"new_conversation", triggerScope:"dm", allowedActions:["reply_text"] }. triggerType ∈ new_conversation|story_mention; triggerScope ∈ dm|story. |
GET /automation-engine/triggers | List (query integrationId). |
PUT · DELETE /automation-engine/triggers/:id | Updates / deletes. |
POST /automation-engine/preview | Dry run, no side effects. |
GET /automation-engine/executions | Execution history. |
There is also an older scheduled tasks module (AI/fixed follow-up messages) mounted at /api/v1/tasks: GET/POST /tasks, POST /tasks/update, /tasks/delete, /tasks/switch.
Sequences & Broadcasts
Sequences (drip) — /sequences
GET / · GET /:id · POST / ({ name, steps[] }) · PUT /:id · DELETE /:id · PATCH /:id/activate · POST /:id/enroll ({ recipientId, pageId }) · GET /:id/enrollments.
Broadcasts (one-to-many) — /broadcasts
GET / · POST / ({ message | templateId, filterTags?, scheduledAt? }) · GET /:id · POST /:id/send (queues) · DELETE /:id. Delivery is worker-driven; /send only changes the state to sending.
Keywords & Workflows
Keywords (keyword auto-reply) — /keywords
GET / · GET /:id · POST / · PUT /:id · PATCH /:id/toggle · DELETE /:id · POST /match-preview ({ integrationId, text }).
On POST /: only keyword is required. matchType ∈ exact|contains|starts_with|regex (default contains); responseType ∈ dm|comment|both (default dm).
dmMessage is required when responseType is dm (the default!) or both; commentReply is required when it is comment or both. A minimal POST { keyword } → 400 "dmMessage is required for dm/both response types".
Workflows — /workflows
GET / · GET /:id · POST / ({ name, steps[], triggerConfig }) · PUT /:id · DELETE /:id · PATCH /:id/activate.
Scheduled messages
/schedule, Bearer. POST /create ({ chat_id, name, description, dateTime, service } + repeat?) · POST /update ({ id, … }) · POST /delete ({ id }) · GET /by-conversation?chat_id=&service=.
Leads & tags
Conversations become leads, with tags and a kanban board. Bearer.
GET /leads | List with filters (channel, tags, score, date, search, page). |
GET /leads/canban · /leads/canban/tag | Kanban board · column per tag. (the route is literally spelled canban.) |
POST /leads/create-tag · GET /leads/tags | Creates a tag ({ tag, description, color? }) · list. |
POST /leads/set-tag | Sets a tag ({ tag_id, tag_name, chat_id, type, service }). |
POST /leads/pause | Pauses the bot on a lead ({ chat_id, service }). |
POST /leads/analyze | AI analysis ({ page_id, recipient_id, platform }). |
GET /leads/meta · /leads/meta/stats | Enriched Meta leads + statistics. |
amoCRM
Synchronization with amoCRM. Bearer. Reads return "bare" objects ({pipelines}, {leads}); writes return { success }.
GET /amocrm/status | Connection status. |
POST /amocrm/connect · /connect-oauth | /connect: { subdomain, access_token } required (client_id/client_secret optional). /connect-oauth: { code, subdomain, client_id, client_secret } required. |
POST /amocrm/logout | Disconnect. |
GET /amocrm/pipelines | Pipelines (the statuses come in _embedded.statuses). |
GET /amocrm/statuses?pipeline_id= | Statuses — pipeline_id required. |
POST /amocrm/statuses | Saves the pipeline + selected statuses. |
GET / POST /amocrm/leads | List / create leads. |
GET /amocrm/leads/:id · PATCH /leads/:id | Detail · update (it is PATCH). |
POST /amocrm/leads/:id/move · /note · /tags | Move · note ({ text }) · tags ({ tags_to_add, tags_to_delete }). |
POST /amocrm/tasks | Task on a lead ({ lead_id, text }). |
GET /amocrm/custom-fields · /users | Custom fields · users (for mapping + responsible_user_id). |
PATCH /amocrm/contacts/:id | Updates a contact (e.g. { custom_fields_values }) — useful for filling in the phone/email after creation. |
Bitrix24
CRM sync — POST /bitrix-crm/connect ({ webhookUrl }) · GET /bitrix-crm/status · GET /bitrix-crm/statuses · DELETE /bitrix-crm/disconnect. All Bearer.
Chat channel (Bitrix24 Open Channels) — separate from the CRM sync, registers an imbot at /bitrix: POST /bitrix ({ data: { botName, clientId, webhookUrl } }) · POST /bitrix/remove · GET /bitrix/getDataBitrix.
Google Sheets
The agent writes leads as rows in a sheet. On connection a builtin tool is attached. Bearer.
GET /google-sheets/connect-info | The robot email you share the sheet with. |
POST /google-sheets/test-connection | Checks access. |
POST /google-sheets/connect | Connects + attaches the tool ({ assistantId, spreadsheetUrl, fields, sheetTab?, dedupeKey? }). |
GET / · /for-assistant/:id | List · status per agent. |
PATCH /:id · POST /:id/test-row · DELETE /:id | Edit mapping · test · disconnect. |
Altegio — online booking
Connects a salon's Altegio calendar. The customer only pastes the Company ID; 5 tools are attached (services, availability, create, cancel, reschedule). Bearer.
| Field | Description | |
|---|---|---|
agentId | required | The agent the tools are attached to. |
companyId | required | The salon's ID in Altegio. |
userToken | optional | Enables cancelling/rescheduling. Stored encrypted. |
bookingEnabled | optional | Allows creating bookings. |
Response: { integrationId, companyId, salon, servicesCount, bookingEnabled, attached }. Status: GET /altegio/for-assistant/:agentId; disconnect: DELETE on the same route.
Telegram Leads · SMS · other integrations
| Integration | Endpoints |
|---|---|
Telegram Group Leads /telegram-leads | GET /connect-info · GET / · POST /start-link · GET /status?assistantId= · GET /for-assistant/:id · PATCH /:id · POST /:id/test · DELETE /:id. |
| SMS (Infobip) | Exposed as a builtin tool (builtin_type: "sms_send"), not as a REST route — the key stays on the server. |
Shopify /shopify | POST /logout (disconnect). |
Wix /wix | POST / (connect) · POST /logout. |
Jivo /jivo | POST / ({ data: { providerId } } — providerId required) · POST /remove · GET /getDataJivo. |
Knowledge base (RAG)
Feed the agent with sources — text, documents, websites, Q&A pairs. Bearer, responses { success, data }.
GET /knowledge/sources · /sources/:id | List (filters assistantId, status, sourceType) · a single one. |
POST /knowledge/sources | Create. Required: sourceType (∈ text|document|website|qa_pairs) + name. content only for text; websiteUrl only for website; document/qa_pairs require neither. |
POST /knowledge/sources/upload | From an uploaded file (≤10 MB). |
PUT /knowledge/sources/:id · DELETE /:id | Update (re-chunk) · delete. |
POST /knowledge/sources/:id/resync · /reextract-products | Re-chunk/re-embed · re-extract products. |
GET /knowledge/sources/:id/products · /chunks | Read the extracted products (pairs with re-extract) · the source's (re-)generated chunks. |
GET / PUT /knowledge/config/:assistantId | Per-agent RAG config. |
Files & documents
Documents in an agent's index (pdf, doc, docx, txt, csv, json, md, markdown, rtf, log, text; ≤5 MB/file). Unlisted extensions are attempted, not rejected — only a missing extension → 400. Bearer.
POST /files | Upload (multipart field files + asistantId). Automatically async if one file is ≥300 KB or the total is ≥700 KB (or forced with asyncUpload=true): 202 + jobId; below the thresholds: 200 sync. |
GET /files/upload-status/:jobId | Indexing progress. |
GET /files?asistantId= · POST /files/delete | List · delete ({ fileId, asistantId }). |
Website scraping → searchable index
Extracts a website's content and makes it searchable (pages, products). /scrape, Bearer.
POST /scrape | Start a scrape ({ url, selector_url_override?, sitemap_urls_override_str? }). |
GET /scrape/search?query_text=&page_size=&page_number=&index= | Paginated full-text search (all parameters required). |
GET /scrape/list · /status/:index | Scraped websites · a job's status (404 if missing). |
GET /scrape/:index?offset=&limit= | Rows/products in an index (default offset 0, limit 100). |
PUT /scrape/:index · DELETE /:index | Update an entry ({ key, data }) · delete an entry ({ key }). |
DELETE /scrape/delete/:index | Delete a website's entire index. |
Analytics & Dashboard
Analytics — /analytics (Bearer; "bare" responses)
GET /messages-per-day?days= ([{ date, incoming, outgoing }]) · GET /top-agents?days= · GET /overview ({ messagesToday, messagesThisWeek, activeThreads, total_contacts, … }).
Dashboard — /api/v2/dashboard (recommended) or /api/v1/dashboard
All GET, Bearer, short cache, rate limit 60/min: /stats?period= · /trend?days= · /pages · /leads/recent?limit= · /alerts · /comments-stats?period= · /cost-detail?period=.
Usage & credits
/usage, Bearer. Query period = 1d/7d/30d/all (default 30d).
GET /usage/summary?period= | { credits:{ remaining, limit, used_in_period, used_today }, messages, tokens, cost_usd, wallet }. |
GET /usage/breakdown | By model, source, agent, day. |
GET /usage/transactions | Credit transaction ledger (limit ≤ 500). |
GET /settings/credit-history | Credit history + subscription + 30-day chart. |
Voice (ElevenLabs)
Voice cloning and management. /create and /update use multipart/form-data. Bearer.
GET /eleven-labs · /voice?eleven_id= | List voices · one voice. |
POST /eleven-labs/create | Create a voice — multipart, fields files (repeatable) + name required; optional description, labels (JSON). |
POST /eleven-labs/update | Edit a voice (multipart): eleven_id + name required; files optional (present → adds samples; absent → metadata only). |
POST /eleven-labs/delete | Delete a single sample from a voice ({ eleven_id, sample_id }). |
POST /eleven-labs/voice/delete | Delete the whole voice ({ id }). |
GET /eleven-labs/available-voices | Voices available to the account. |
The multipart field name is exactly files (repeated for multiple samples). Sent literally as files[], multer rejects it with an "Unexpected field" error.
Credits — how they are calculated
The credit is the billing unit. The number of credits for an AI response depends ONLY on the model used — not on how many tokens were consumed. It is a flat per-message charge, per model.
credite_pe_mesaj = pret_flat_al_modelului (+ suprataxe media, doar FB/IG)Tool calls and RAG/knowledge add no credits — a message with 5 tool calls costs the same as a simple one.
Price per model
| Credits / message | Models (examples) |
|---|---|
| 1 | GPT-4o mini, 4.1, 4.1 nano, DeepSeek V3, Gemini Flash Lite |
| 2 | 4.1 mini, 5 nano, Llama 3.3 70B / Llama 4 Maverick, Grok 3 mini, Gemini Flash |
| 3 | 5.4, 5.4 mini, 5 mini, 5.5 mini, Kimi K2.5 |
| 4 | GPT-5, 4o, GPT-OSS 120B, Cohere Command A |
| 5 | GPT-5.1, Mistral Large 3, Grok 4 Non-Reasoning |
| 8 | GPT-5.2, Gemini 3 Pro / 3.1 Pro |
| 10–12 | GPT-5.3, GPT-5.5, Grok 4 fast, 5.6 Terra/Luna |
| 25 | Grok 4 |
Media surcharges (FB/IG only)
On top of the model price: incoming voice message (transcription) +1; image generation +5/image; ElevenLabs TTS voice reply +20 (other TTS/STT +10).
Plans & limits
| Plan | Price | Included credits |
|---|---|---|
| — (no subscription) | 0 | 0 (+ initial credit balance at sign-up) |
| standard | €49 | 2.000 |
| pro | €150 | 10.000 |
| ultra | €299 | 30.000 |
| business | €499 | 50.000 |
- Balance =
limit − current(fromUserSettings.tokens). - On subscription activation:
limit = max(limit, current + credite_plan). - Top-up one-time:
credite = floor(tokeni_cumpărați / 3). - When exhausted (
current ≥ limit) →402 credits_exhausted. - New accounts start with an initial credit balance; without an active subscription there are no recurring credits.
Example: an FB message with an agent on Grok 4 = 25 credits; an IG voice message with gpt-5.4 (3) + transcription (+1) + ElevenLabs TTS reply (+20) = 24 credits; a plain text = between 1 and 25 credits, depending on the model.
Reading the status (balance / usage)
The read endpoints are documented in Usage & credits (GET /usage/summary, GET /settings/credit-history) and Billing (GET /stripe/billing/overview, GET /stripe/billing/dashboard).
Account & profile
/users, Bearer.
GET /users/profile | Full profile: user, plan_id, remaining credits, Stripe status, subscription + days to expiry, IG/FB channels. |
POST /users/update | Update full_name, bio, email (also synced to Stripe). |
POST /users/change-locale | UI language (locale). |
POST /users/me/delete-request · /me/delete-cancel | GDPR: account deletion request (30-day grace period) / cancellation. delete-request requires { confirm_email } (= the account email, otherwise 400); reason optional → { success, scheduled_at, grace_ends_at }. |
Billing (Stripe)
/stripe, Bearer. (The /stripe/webhook webhook is public/signed — you do not call it.)
POST /stripe/create-checkout-session | Subscription checkout ({ product } = lookup_key, e.g. pro_month). |
POST /stripe/create-checkout-session-on-token | Credit top-up checkout — body { unit_amount_decimal } (amount in USD; grants amount×100 credits). |
GET /stripe/create-billing-portal | Stripe billing portal link. |
GET /stripe/billing/overview · /stripe/billing/dashboard | Plan + credits + forecast. |
GET /stripe/billing/invoices · /billing/charges · /billing/upcoming | Invoices · payments · upcoming invoice. |
POST /stripe/billing/invoices/:id/retry | Retry payment of an invoice. |
GET /stripe/history | Purchase history. |
Chatbots — channel ↔ agent mapping
A "chatbot" links a channel (FB page, IG, widget…) to an agent. /chatbots, Bearer.
GET /chatbots | List. |
POST /chatbots/create · /update | Create / update a mapping (platform ↔ assistant_id). |
POST /chatbots/disable · /delete | Toggle enabled · delete. |
POST /chatbots/settings · /comments | Bulk settings (auto-reply, name, agent) · comment replies. |
POST /chatbots/master-toggle · GET /master-status | Global on/off for all channels · status. |
POST /chatbots/service-bulk-toggle · GET /service-status | On/off per service · counters. |
Teams & referral
Teams (multi-user) — /teams
Multiple operators on one account, with roles (admin/agent/viewer). Bearer.
POST /teams · GET /teams | Create a team ({ name } — only one owned team per user; a 2nd → 400) · list teams (owned + member). |
GET /teams/:id · PUT /teams/:id | Details + members · rename (owner). |
POST /teams/:id/invite | Invite by email ({ email, role }). |
PUT /teams/:id/members/:memberId/role · DELETE /members/:memberId | Change role · remove member. |
POST /teams/accept-invite | Accept pending invitations. |
Referral — /referrals
Referral program with promo codes and a Stripe Express account. Bearer.
GET /referrals/referrals-info | Dashboard: balance, promo codes, number of referrals, earnings. |
POST /referrals/create-stripe-account · GET /dashboard | Stripe Express account + coupon · dashboard/onboarding link. |
POST /referrals/create-promocode | Create a promo code on your coupon ({ code }). |
POST /referrals/deactivate-promocode | Deactivate / reactivate a promo code — reversible toggle ({ code }). |
POST /referrals/delete-promocode | Deletes the code permanently (removes it from the list + Stripe active:false; irreversible). |
POST /referrals/create-checkout-session | Subscription checkout with a referral code. |
999.md (Simpals)
Integration with the 999.md marketplace. /trei9, Bearer.
POST /trei9/auth · /unlink | Link the account ({ username_simpals, refreshToken }) · disconnect. |
GET /trei9/threads · /thread?chat_id= | Contacts · a thread's messages. |
POST /trei9/send-message · /edit-config | Send a message · per-thread config (AI on/off). |
Forms & support
/storage. Support tickets and forms (some public, for contact pages).
POST /storage/help-requests 🔒 | Support ticket + attachments (Telegram notification). |
POST /storage/callback 🔒 | Request a callback. |
POST /storage/contact-us · /get-demo · /cv · /enterprise public | Public contact/demo/CV/enterprise forms. Rate limit 1/10min only on /get-demo and /cv; /contact-us and /enterprise have no limit. |
Agent quality (audit)
Automatic audit of an agent's conversations, with scores and recommendations. Bearer.
GET /agents/:id/quality · /quality/:auditId | Recent audits · detail (scores + excerpts). |
POST /agents/:id/quality/run | Run an audit manually. |
POST /agents/:id/quality/:auditId/apply | Adds the recommendations to the agent's instructions. |
Events & monitoring
GET /events?userId= public | Real-time SSE stream per user (new messages, notifications; 10s heartbeat). |
GET /monitoring?start=&end= 🔒 | Per-user usage: tokens, daily, TTS/STT voice, images. |
Audit log · Changelog · Public content
GET /audit?page=&limit=&action=&entity_type=&from=&to= 🔒 | Your account's action log (paginated, filterable). GET /audit/actions = the action types. |
GET /changelog · /latest · /seen 🔒 | The product's news feed + the "seen" state. POST /changelog/seen marks it as read. |
GET /posts/news · /news/:slug · /blogs · /blogs/:slug public | Public news/blog content (marketing site) — ?locale= parameter. |
Page posts (Facebook & Instagram)
Publish and schedule content on connected pages. Bearer; proxy to Meta Graph.
Facebook — /page-posts
POST /page-posts | Text/link post (message OR link; optional published, scheduled_publish_time between 10 min and 6 months). |
POST /page-posts/photo | Photo post (url; optional message, scheduled_publish_time). |
PUT /page-posts/:postId · DELETE /:postId | Edit the text · delete. |
GET /page-posts?limit=&after= | List feed posts (limit default 25, max 100). |
Instagram — /ig-publish
POST /ig-publish/post | Image post (image_url; optional caption, location_id, user_tags). |
POST /ig-publish/video · /reel | Feed video / Reel (video_url; optional caption, thumb_offset, share_to_feed). |
POST /ig-publish/carousel | Carousel of 2–10 items (items[]; optional caption). |
POST /ig-publish/story | Story (image_url OR video_url). |
Native IG scheduling is unreliable (Meta often ignores it) — for IG, publish at the desired time from your own scheduler.
FB/IG comments
Moderation and AI auto-reply on Facebook/Instagram comments, with an approval queue, templates and trigger rules. /comments, Bearer.
Settings & moderation
GET /comments/settings · PUT /settings | Pages + auto/private reply toggles · update the toggles. |
GET /comments/health · /diagnostics | Page health diagnostics (token, 30-day stats). |
GET /comments/recent | The latest 50 comments across all pages. |
POST /comments/:id/hide · /reply · DELETE /:id | Hide · manual reply (≤8000) · delete. |
POST /comments/bulk-hide | Bulk hide/unhide (max 25). |
POST /comments/media/:mediaId/toggle-comments | Enable/disable comments on an IG post ({ enabled, page_id }). |
POST /comments/settings/:chatbotId/archive · /restore | Archive / restore a page in the settings list. |
AI approval queue
GET /comments/pending · /pending/count | Pending AI drafts + counter for the badge. |
POST /comments/:id/approve · /:id/reject | Approve & publish (optional edited_text) · reject. |
POST /comments/chatbots/:id/comments-config | Mode (off/auto/approval) + AI instructions per chatbot/agent. |
Templates & trigger rules
/comments/predefined-replies (CRUD) | Public reply templates. |
/comments/predefined-pm-messages (CRUD) | Private message templates. |
/comments/trigger-rules (CRUD) | Auto-reply rules (matchType exact/contains/regex → reply/PM). |
POST /comments/trigger-rules/match-preview | Test a rule on a text, without saving. |
Live comments (FB Live)
GET /live-comments/:videoId/stream | SSE stream of live comments (Graph polled every 2s). |
GET /live-comments/:videoId | Recent live comments (non-stream, with an after cursor). |
POST /live-comments/:id/hide · DELETE /:id | Hide · delete. |
Support
aichat.md dashboard
Create the agent, get the widgetId, configure channels and allowed origins.
Technical contact
For access, dedicated integrations or API questions — write to us via the form in the dashboard or POST /storage/contact-us.
Turnkey integration
A complex scenario (custom tools, multiple channels, CRM)? The team will help you.
Programmatic bug reporting: POST /api/v1/bug-report — multipart with up to 10 screenshots (screenshots), rate limit 5/min; attaches the user if you send a JWT, otherwise anonymous.