Уведомления
In-app уведомления («колокольчик») — персональная лента событий, адресованных конкретному пользователю кабинета (например, назначение на сделку или на задачу по сделке). Это REST-сторона того же потока, что в реальном времени приходит событием notification.created по персональному WebSocket-каналу пользователя: сокет нужен для живого пуша и бейджа, а эти эндпоинты — чтобы подгрузить историю при загрузке приложения, узнать число непрочитанных и сбросить бейдж.
Скоуп ленты
Лента скоупится по текущему пользователю и его активной компании (current_company_id): список, счётчик и «отметить всё» возвращают только уведомления активной компании; пометка одного уведомления прочитанным/непрочитанным тоже ищет его среди уведомлений активной компании (чужое, из другой компании или несуществующее — 404).
Объект уведомления
Объект уведомления содержит поля:
id— строка UUID.type— строковый идентификатор вида уведомления в snake_case. Текущий каталог:conversation_assigned,call_missed,channel_disconnected,channel_banned,export_completed,export_failed,template_approved,template_rejected,broadcast_finished,broadcast_failed,history_sync_completed,ai_action_pending,deal_assigned,recording_retention_changed,recording_switched_off,recording_switched_on,task_assigned,task_reminder,task_reminder_unassigned(с 2026-09-26 триtask_*заменили прежнийdeal_task_assigned, который больше не отправляется),lead_unsortedиlead_cap_reached(с 2026-09-29: новые авто-лиды в «Неразобранном» воронки — одна строка на воронку с текущим числом лидов, получают держателиdeal.update, строка помечается прочитанной, когда колонку разобрали; достигнут часовой лимит авто-лидов компании — администраторам, раз в час) и три типа ИИ-сотрудников (с 2026-10-10):ai_agent_budget_warning(агент израсходовалbudget_warning_pctмесячного лимита, раз в месяц на агента, администраторам),ai_handoff_waiting(ИИ передал диалог людям, и никто не ответил заhandoff.notify_after_minutes, участникам диалога),ai_model_retired(провайдер перестал отдавать модель агента; при ключе компании администратор должен выбрать новую, администраторам); с 2026-10-15 ещё два типа ночной проверки ИИ-сотрудников:ai_review_enabled(платформа включила проверку для занятых агентов компании, администраторам, до первого прогона;data:instance_idsиdisable={method, path, body}для кнопки «Выключить» —PUT /v1/ai-agents/review-settingsс{enabled: false}) иai_review_proposed(менеджер предложил решение по вопросу проверки, администраторам;data:issue_id,instance_id, без заголовка и фрагментов диалога). Утренний дайджест проверки (ai_review_digest, счётчики и ссылка, без текстов вопросов) уходит не в ленту, а по e-mail и WhatsApp администраторам, если это не выключено вreview-settings; набор будет расширяться, поэтому неизвестныйtypeпоказывайте поtitle/body, а не отбрасывайте.category— раздел, к которому относится уведомление:inbox,calls,deals,tasks,channels,broadcasts,automations,data,team,billing,system. Поле есть всегда — удобно для группировки и фильтров в своём UI.severity— важность:info,success,warning,critical. Тоже всегда присутствует.title— короткий заголовок.body— строка илиnull.action_url— путь внутри кабинета, куда ведёт уведомление, либоnull(уведомление чисто информационное, открывать нечего). Путь относительный, без домена, и может отличаться у разных получателей одного и того же события: например, у пропущенного звонка (call_missed) со связанным диалогом это всегда сам диалог (/chat?conversation_id=...), а без диалога — очередь перезвона на пульте (/telephony?tab=console&callback=...) для тех, кто может управлять телефонией (правоcompany_telephony.manage), и журнал звонков (/chat?filter=calls&call_id=...) для остальных.group_key— ключ схлопывания однотипных уведомлений (например, несколько пропущенных звонков одного контакта обновляют одну строку вместо того, чтобы плодить новые), либоnull.data— объект с полями, зависящими отtype(например,deal_id/deal_titleдляdeal_assigned,call_id/contact_id/conversation_id/missed_last_hourдляcall_missed; уtask_*—deal_task_id,deal_id,company_id,assigned_user_id,unassigned, плюсtask_title/assigned_byуtask_assignedиcount/last_deal_task_idуtask_reminder_unassigned; уlead_unsorted—count,funnel_id,last_deal_id, уlead_cap_reached—limit).read_at— ISO-время прочтения илиnull, если не прочитано.expires_at— ISO-время, после которого уведомление считается неактуальным, либоnull.created_at— время создания.
Форма ответа списка и параметры
Важно про форму ответа списка: GET /v1/notifications отдаёт стандартный конверт пагинатора Laravel — массив под ключом data, а мета-поля пагинации (current_page, per_page, total, last_page, next_page_url и т.д.) лежат плоско на верхнем уровне. Это отличается от большинства REST-групп, где список приходит как data:{ items, pagination }.
Параметры запроса: filter=unread|all (по умолчанию — все; иное значение отклоняется с 422), category (одно из значений category, см. выше; неизвестное — 422), categories[] (несколько значений category сразу, например categories[]=deals&categories[]=tasks; сочетается с category по И), q (поиск без учёта регистра по title и body, 2–100 символов; % и _ ищутся буквально) и per_page (размер страницы, 1–100, по умолчанию 20). Обратите внимание на snake_case per_page (не perPage). Уведомления с истёкшим expires_at не попадают ни в список, ни в счётчик.
Платформенные уведомления (`GET /platform-notifications`)
GET /platform-notifications (2026-08-24) — не путать с персональной лентой «колокольчика» выше: это company-scoped (не per-user) плоский журнал технических уведомлений от самой платформы AISAR (пока единственный вид — «канал отключился», из NotifyChannelDisconnectedJob), заменивший собой прежнюю механику служебных диалогов в чате. Право доступа — conversation.view (то же, что у семейства /conversations), окно выдачи — последние 90 дней, сортировка — новые сверху, пагинация постраничная (page/limit, limit 1–50, по умолчанию 20; блок pagination {page, perPage, total, lastPage}). Лимитер — 120 запросов в минуту.
Элемент списка: id, kind (тип уведомления, например channel_disconnected), reason (человекочитаемая причина или null), created_at, channel ({id, name, type} либо null — null, если канал с тех пор удалён; событие само по себе не исчезает вместе с каналом), conversation_id и message_id (для обратной совместимости с уведомлениями, унаследованными от прежней механики; null для новых записей). «Прочтение» ленты — не по элементам, а курсором: PATCH /me/ui-preferences с aisar_notifications.seen_until (ISO-datetime либо null) продвигает границу непросмотренного, которую использует счётчик counters.aisar_notifications в группе «Диалоги» (GET /conversations/counters).
Эндпоинты
| Метод | Путь | Описание |
|---|---|---|
| GET | /v1/notificationsЛента уведомлений пользователя в активной компании (конверт пагинатора Laravel). Параметры: `filter=unread|all`, `category`, `categories[]`, `q` (2–100 символов), `per_page` (1–100, по умолчанию 20). | Лента уведомлений пользователя в активной компании (конверт пагинатора Laravel). Параметры: `filter=unread|all`, `category`, `categories[]`, `q` (2–100 символов), `per_page` (1–100, по умолчанию 20). |
| GET | /v1/notifications/unread-countЧисло непрочитанных уведомлений пользователя в активной компании. Ответ: `{ "count": N, "critical": M, "by_category": { "<category>": n } }` — `critical` считает только непрочитанные с `severity: "critical"`; `by_category` — непрочитанные по разделам, только с n>0 (`{}`, если непрочитанных нет). | Число непрочитанных уведомлений пользователя в активной компании. Ответ: `{ "count": N, "critical": M, "by_category": { "<category>": n } }` — `critical` считает только непрочитанные с `severity: "critical"`; `by_category` — непрочитанные по разделам, только с n>0 (`{}`, если непрочитанных нет). |
| POST | /v1/notifications/{notification}/readОтметить одно уведомление прочитанным (по его UUID). Ответ: `{ "status": "ok" }`; 404, если id не UUID, уведомление не найдено либо принадлежит другому пользователю или компании. | Отметить одно уведомление прочитанным (по его UUID). Ответ: `{ "status": "ok" }`; 404, если id не UUID, уведомление не найдено либо принадлежит другому пользователю или компании. |
| POST | /v1/notifications/{notification}/unreadВернуть уведомление в непрочитанные (по UUID). Ответ: `{ "status": "ok" }`; 404 для чужого или неизвестного id. | Вернуть уведомление в непрочитанные (по UUID). Ответ: `{ "status": "ok" }`; 404 для чужого или неизвестного id. |
| POST | /v1/notifications/bulkМассово отметить уведомления: тело `{ "ids": [uuid, ...] (1–100), "action": "read"|"unread" }`. Ответ: `{ "updated": n }`; id других пользователей и компаний молча игнорируются. | Массово отметить уведомления: тело `{ "ids": [uuid, ...] (1–100), "action": "read"|"unread" }`. Ответ: `{ "updated": n }`; id других пользователей и компаний молча игнорируются. |
| POST | /v1/notifications/read-allОтметить прочитанными все непрочитанные уведомления пользователя в активной компании. Ответ: `{ "status": "ok" }`. | Отметить прочитанными все непрочитанные уведомления пользователя в активной компании. Ответ: `{ "status": "ok" }`. |
| GET | /v1/platform-notificationsCompany-scoped журнал платформенных уведомлений (например «канал отключился») за последние 90 дней. | Company-scoped журнал платформенных уведомлений (например «канал отключился») за последние 90 дней. |
Примеры
Список непрочитанных уведомлений
Запрос
curl -X GET "https://api.aisar.app/v1/notifications?filter=unread&per_page=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"current_page": 1,
"data": [
{
"id": "3f1e6a20-0c7b-4a9e-b2d1-8a5c1e000001",
"type": "deal_assigned",
"category": "deals",
"severity": "info",
"title": "You were assigned to deal \"Website redesign\"",
"body": "Assigned by Aigerim",
"action_url": "/deals/42",
"group_key": null,
"data": {
"deal_id": 42,
"deal_title": "Website redesign",
"assigned_by": 3
},
"read_at": null,
"expires_at": null,
"created_at": "2026-02-10T09:15:00.000000Z"
}
],
"first_page_url": "https://api.aisar.app/v1/notifications?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "https://api.aisar.app/v1/notifications?page=1",
"next_page_url": null,
"path": "https://api.aisar.app/v1/notifications",
"per_page": 20,
"prev_page_url": null,
"to": 1,
"total": 1
}Счётчик непрочитанных
Запрос
curl -X GET "https://api.aisar.app/v1/notifications/unread-count" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"count": 3,
"critical": 1,
"by_category": { "deals": 2, "tasks": 1 }
}Отметить одно уведомление прочитанным
Запрос
curl -X POST "https://api.aisar.app/v1/notifications/3f1e6a20-0c7b-4a9e-b2d1-8a5c1e000001/read" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"status": "ok"
}Вернуть уведомление в непрочитанные
Запрос
curl -X POST "https://api.aisar.app/v1/notifications/3f1e6a20-0c7b-4a9e-b2d1-8a5c1e000001/unread" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"status": "ok"
}Массовая отметка уведомлений
Запрос
curl -X POST "https://api.aisar.app/v1/notifications/bulk" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["3f1e6a20-0c7b-4a9e-b2d1-8a5c1e000001", "3f1e6a20-0c7b-4a9e-b2d1-8a5c1e000002"], "action": "read"}'Ответ
{
"updated": 2
}Отметить все прочитанными
Запрос
curl -X POST "https://api.aisar.app/v1/notifications/read-all" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"status": "ok"
}Журнал платформенных уведомлений
Запрос
curl -X GET "https://api.aisar.app/v1/platform-notifications?limit=20" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"Ответ
{
"data": {
"items": [
{
"id": 340,
"kind": "channel_disconnected",
"reason": "logged_out",
"created_at": "2026-08-24T09:12:00.000000Z",
"channel": { "id": 3, "name": "Основной WhatsApp", "type": "whatsapp" },
"conversation_id": null,
"message_id": null
}
],
"pagination": { "page": 1, "perPage": 20, "total": 1, "lastPage": 1 }
}
}