Embedding AISAR chat
How to embed AISAR's full-featured chat into a third-party application via iframe: authentication, three display modes, and a full integration walkthrough.
AISAR provides an embeddable chat — a full-featured messenger that loads inside an <iframe> in your application. The user works with the chat inside your UI without leaving it.
Architecture
Ваше приложение (бэкенд)
│
├─ 1. Создать API-токен (однократно, через AISAR UI)
├─ 2. Получить список юзеров GET /v1/users
├─ 3. Найти контакт по телефону GET /v1/contacts/lookup?q=...
├─ 4. Получить список каналов GET /v1/channels
├─ 5. Отправить сообщение POST /v1/messages/send
│
├─ 6. Авторизовать юзера POST /v1/embed/auth
│ → получить session token
│
└─ 7. Открыть iframe
https://my.aisar.app/embed/chat?token=<session_token>&...Steps 2–3 are cached on your side. Step 4 runs every time the iframe opens (the token lives 8 hours). Every API request uses the base URL https://api.aisar.app with the /v1 prefix and the Authorization: Bearer <api_token> header, issued once in the AISAR UI (Settings → API tokens; the section is available to companies on a business plan with the API feature enabled). A partner receives the key when a company is connected.
Endpoints
List users
Returns every real user of the company (not service accounts) — use it to map your users to AISAR users.
GET /v1/users
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json{
"data": [
{
"id": 1,
"name": "Алексей",
"lastname": "Петров",
"email": "alexey@company.kz",
"availability": "online",
"last_login_at": "2026-03-15T10:30:00.000000Z"
}
]
}id (used in embed/auth), name, lastname, email, availability (online/offline/away).
Search contacts
Search by phone number, external ID, or username. The result gives you the contact_id used in the iframe URL (mode 3).
GET /v1/contacts/lookup?q=<query>
Authorization: Bearer YOUR_API_TOKEN
Accept: application/jsonq (required, 2–255 chars) searches the contact's phone number (+12025550187), account external ID (12025550187@s.whatsapp.net), and username (alexey_petrov), fuzzy match (ILIKE). Limit: max 20 results, groups and merged contacts are excluded.
{
"data": [
{
"id": 42,
"company_id": 1,
"firstName": "Алексей",
"lastName": "Петров",
"displayName": "Алексей Петров",
"status": "active",
"phones": [{ "phone": "+12025550187", "type": "mobile" }],
"contactAccounts": [
{ "id": 10, "account_type": "whatsapp", "external_id": "+12025550187", "is_group": false }
],
"tags": []
}
]
}List channels
Returns the company's channels available to the current user — to obtain the channel_id needed when sending messages.
GET /v1/channels
Authorization: Bearer YOUR_API_TOKEN
Accept: application/jsonQuery parameters (all optional): type (channel type), status (active/inactive), channel_type_id, setup_state, include_setup_drafts (defaults to false).
Send a message
Sends an outbound text message to a contact through the given channel; if the conversation doesn't exist, it is created automatically.
POST /v1/messages/send
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json
{
"channel_id": 3,
"recipient": "+12025550187",
"message_content": "Здравствуйте! Ваш заказ готов."
}channel_id (required, from GET /v1/channels), recipient (required, the recipient's phone number or external_id, format depends on channel type), message_content (required, max 65,535 chars). Response 200: { "success": true }. The full reference for this endpoint (attachments, voice notes, WhatsApp groups, templates) is in the "Sending messages: endpoint reference" guide.
Embed authorization
Creates a short-lived session token for a specific user — this token is passed in the iframe URL.
POST /v1/embed/auth
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json
{
"user_id": 1
}{
"token": "YOUR_EMBED_SESSION_TOKEN",
"redirect_url": "https://my.aisar.app/embed/chat"
}user_id must belong to a real user of the same company as the API token — service accounts and platform administrators are not allowed. token lives 8 hours. Errors: 400 — user_id missing; 403 — the call was made not with the company's full API token but with a narrow token of its service account (for example an embedded-chat token), error: "This token cannot issue embed sessions"; 422 — user not found in the company. Since 2026-09-28 the issued token is bound to the company that minted it: requests with it always run in that company, whatever the user's current company is; it does not work on platform endpoints (403), and PUT /v1/me/current-company to another company answers 403 with error: "token_company_bound". If the user stops being a member of the company, an already issued token stops working too (403).The issued session token has a limited embed:read scope and lives 8 hours — enough to run the embedded chat, but it does not replace a full API token.
Some endpoints refuse an embed token outright — 403 — regardless of what its scope lists. Currently closed this way: the company-wide analytics dashboard (revenue, deals, conversation and message volumes, per-operator load), call-transcript search (q_in=transcript|all on GET /v1/calls), and the missed-call callback queue (/v1/telephony/callbacks*). The general rule: if a surface returns a company-wide commercial summary or a bulk listing of personal data, a token embedded in someone else's UI cannot reach it — call it from your own backend with a regular API token instead.
CRM-based authorization (Bitrix24 and others)
An alternative mode of POST /v1/embed/auth — for embedding the chat straight from a CRM UI (for example, a Bitrix24 card), when the request is initiated by the app installed in the CRM rather than by your backend with an API token. Instead of user_id, it takes the CRM portal identifier.
POST /v1/embed/auth
Accept: application/json
Content-Type: application/json
{
"type": "bitrix24",
"member_id": "YOUR_CRM_MEMBER_ID",
"payload": {
"AUTH_ID": "PORTAL_ISSUED_ACCESS_TOKEN"
}
}type (required, the CRM type: bitrix24, etc.), member_id (required, the CRM portal identifier), payload (required, must be an object — otherwise 400). For bitrix24, payload.AUTH_ID is required and must be a string (otherwise 400): it's an access token the handler uses to verify the caller's identity against the portal itself (user.current). The response is the same { "token": ..., "redirect_url": ... } envelope as in the user_id mode; the token likewise has the embed:read scope and lives 8 hours.
AUTH_ID, with an invalid one, or if the verified CRM user isn't mapped to an AISAR user or is no longer a company member — 422, no token is issued. There is no more fallback to "the company's first user": a missing or empty payload.AUTH_ID used to silently issue that token; now it only produces a refusal. Any b24_user_id sent in payload is ignored — identity cannot be asserted over the wire, only proven by verifying AUTH_ID against the portal.POST /v1/embed/auth is rate-limited across the whole route: calls carrying a service-account Bearer token (the user_id mode) get up to 120 requests/min per token; anonymous calls in this CRM mode get up to 30 requests/min per IP.
Embedding the iframe
Where you can embed it, and iframe attributes
Since 2026-10-08 the embedded chat https://my.aisar.app/embed/chat?token=... can be placed in an <iframe> on any https site — you do not need to register the domain in AISAR. Without a token in the URL the page refuses to be embedded. The token is issued by the integrator's server: POST /v1/embed/auth with the API key of the company's service account (in the cabinet: Settings, API tokens) and the employee's user_id. The token is bound to that employee and company, grants rights only for running the embedded chat, and lives 8 hours; keep it on the server, never log it, and the page removes the token from the address bar itself after loading.
Recommended iframe attributes: allow="microphone; clipboard-write" (voice messages, calls from the embedded chat, and copying to the clipboard). Better not to set sandbox; if you need it, allow allow-scripts allow-same-origin allow-popups allow-forms allow-downloads.
<iframe
src="https://my.aisar.app/embed/chat?token=<session_token>"
allow="microphone; clipboard-write"
style="width: 100%; height: 100%; border: none;"
></iframe>Three display modes
Mode 1 — full chat (inbox). A full-featured messenger with a conversation list, filters, search, and a message panel.
<iframe
src="https://my.aisar.app/embed/chat?token=<session_token>"
style="width: 100%; height: 100%; border: none;"
></iframe>Mode 2 — chat with a pre-filled search. The same full chat, but the search box is pre-filled (the search parameter).
<iframe
src="https://my.aisar.app/embed/chat?token=<session_token>&search=+12025550187"
style="width: 100%; height: 100%; border: none;"
></iframe>Mode 3 — contact message panel. Only the message panel for a specific contact, with no conversation list or filters — ideal for embedding inside a customer record. Parameters: contact_id (from /v1/contacts/lookup), view=messages. Behaviour: loads the contact's most recent conversation; if there are none, shows an empty state; the panel is fully functional (sending/editing messages, managing the conversation).
<iframe
src="https://my.aisar.app/embed/chat?token=<session_token>&contact_id=42&view=messages"
style="width: 100%; height: 100%; border: none;"
></iframe>Additional parameters
| Parameter | Description |
|---|---|
lang | Interface language: en, ru, kk. Defaults to ru |
contact_id | Contact ID (without view=messages — filters the conversation list) |
deal_id | Deal ID — opens the chat in the context of a specific deal |
Full integration example (Python)
API_TOKEN = "YOUR_API_TOKEN"
API_BASE = "https://api.aisar.app/v1"
EMBED_BASE = "https://my.aisar.app/embed/chat"
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Accept": "application/json",
}
# Получите список пользователей для маппинга
response = requests.get(f"{API_BASE}/users", headers=headers)
aisar_users = response.json()["data"]
user_mapping = {
"your_user_123": 1, # Алексей → AISAR user ID 1
"your_user_456": 2, # Мария → AISAR user ID 2
}
# Найдите контакты по телефонам (кэшируйте результат)
response = requests.get(
f"{API_BASE}/contacts/lookup",
params={"q": "+12025550187"},
headers=headers,
)
contacts = response.json()["data"] # contacts[0]["id"] = 42 ← contact_id для iframeaisar_user_id = user_mapping["your_user_123"]
response = requests.post(
f"{API_BASE}/embed/auth",
json={"user_id": aisar_user_id},
headers=headers,
)
session_token = response.json()["token"]
# Режим 1: полный чат
iframe_url = f"{EMBED_BASE}?token={session_token}"
# Режим 2: предзаполненный поиск
iframe_url = f"{EMBED_BASE}?token={session_token}&search=+12025550187"
# Режим 3: панель сообщений контакта
iframe_url = f"{EMBED_BASE}?token={session_token}&contact_id=42&view=messages"Recommendations
Caching
| What | How often to refresh |
|---|---|
| API token | Permanent (until revoked) |
| User list | When the team composition changes |
| Contact mapping | On first lookup + as needed |
| Session token | Every time the iframe opens (lives 8 hours) |
iframe sizing
| Mode | Recommended size |
|---|---|
| Full chat (1, 2) | min-width: 800px, min-height: 600px |
| Message panel (3) | min-width: 400px, min-height: 500px |
Security
- Never pass the API token to the frontend. Authorization (
/v1/embed/auth) must happen on your backend. - The session token is passed via the URL — the iframe clears it from the address bar immediately after loading.
- The token in the iframe URL must not end up in your server logs or analytics: keep it only in page memory and issue a fresh one for each opening.
- The session token has limited scope and an 8-hour lifetime.
Error handling
| Code | Cause | Action |
|---|---|---|
| 401 | Invalid/expired API token | Check the API token in AISAR settings |
| 400 | Missing required parameter | Check user_id in embed/auth |
| 422 | User/contact not found | Check the ID and its company membership |
Limitations
- One API token = one company. For several companies, use separate tokens.
contact_idbelongs to the API token's company — contacts from other companies are not accessible.- The iframe works over HTTPS only (except localhost).
- Max 20 results in
/v1/contacts/lookup.