Skip to content
AISARAISAR
REST API

Notifications

In-app notifications (the "bell") are a per-user feed of events addressed to a specific cabinet user (for example, being assigned to a deal or to a deal task). This is the REST side of the same stream that arrives in real time as the notification.created event on the user's personal WebSocket channel: use the socket for the live push and badge, and these endpoints to backfill history on app load, read the unread count, and clear the badge.

Feed scope

The feed is scoped to the current user and their active company (current_company_id): the list, the counter, and "mark all" return only notifications for the active company; marking a single notification read/unread also resolves it within the active company (someone else's, another company's or unknown — 404).

Notification object

A notification object has these fields:

  • id — a UUID string.
  • type — a snake_case string identifying the notification kind. The current catalog: 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 (since 2026-09-26 the three task_* types replace the former deal_task_assigned, which is no longer sent), lead_unsorted and lead_cap_reached (since 2026-09-29: new auto-leads in a funnel's "Unsorted" — one row per funnel carrying the current number of leads, sent to holders of deal.update and marked read once the column is sorted; the company's hourly auto-lead limit was reached — to administrators, once an hour) and three AI-employee types (since 2026-10-10): ai_agent_budget_warning (the agent has spent budget_warning_pct of its monthly limit, once per agent per month, to administrators), ai_handoff_waiting (the AI handed a dialog to people and nobody answered within handoff.notify_after_minutes, to the dialog participants), ai_model_retired (the provider stopped serving the agent's model; for a company-key agent an administrator has to pick a new one, to administrators); since 2026-10-15 two more types of the nightly AI-employee review: ai_review_enabled (the platform switched the review on for the company's busy agents, to administrators, before the first run; data: instance_ids and disable = {method, path, body} for the "switch off" button, PUT /v1/ai-agents/review-settings with {enabled: false}) and ai_review_proposed (a manager suggested a decision on a review issue, to administrators; data: issue_id, instance_id, no title or dialog fragments). The morning review digest (ai_review_digest, counters and a link, no issue texts) does not go to the feed: it is sent by e-mail and WhatsApp to administrators unless switched off in review-settings; the set will grow, so render an unknown type from its title/body rather than dropping it.
  • category — the section the notification belongs to: inbox, calls, deals, tasks, channels, broadcasts, automations, data, team, billing, system. Always present — handy for grouping and filters in your own UI.
  • severity — importance: info, success, warning, critical. Always present too.
  • title — a short heading.
  • body — a string or null.
  • action_url — the in-cabinet path the notification points at, or null (a purely informational row with nothing to open). The path is relative, carries no domain, and may differ between recipients of the same event: for a missed call (call_missed) with a linked conversation it is always that conversation (/chat?conversation_id=...), while without one it is the console's callback queue (/telephony?tab=console&callback=...) for those who can manage telephony (the company_telephony.manage permission) and the call journal (/chat?filter=calls&call_id=...) for everyone else.
  • group_key — the key used to collapse notifications of the same kind (e.g. several missed calls from one contact update a single row instead of piling up new ones), or null.
  • data — an object whose fields depend on type (e.g. deal_id/deal_title for deal_assigned, call_id/contact_id/conversation_id/missed_last_hour for call_missed; the task_* types carry deal_task_id, deal_id, company_id, assigned_user_id, unassigned, plus task_title/assigned_by on task_assigned and count/last_deal_task_id on task_reminder_unassigned; lead_unsorted carries count, funnel_id, last_deal_id, and lead_cap_reached carries limit).
  • read_at — the ISO timestamp when read, or null while unread.
  • expires_at — the ISO timestamp after which the notification is stale, or null.
  • created_at — when it was created.

List response shape and query parameters

Important about the list response shape: GET /v1/notifications returns the standard Laravel paginator envelope — the array under the data key, with the pagination meta fields (current_page, per_page, total, last_page, next_page_url, etc.) sitting flat at the top level. This differs from most REST groups, where a list comes back as data:{ items, pagination }.

Query parameters: filter=unread|all (defaults to all; any other value is rejected with 422), category (one of the category values above; an unknown one returns 422), categories[] (several category values at once, e.g. categories[]=deals&categories[]=tasks; combined with category by AND), q (case-insensitive search in title and body, 2-100 characters; % and _ match literally) and per_page (page size, 1–100, default 20). Note the snake_case per_page (not perPage). Notifications whose expires_at has passed appear in neither the list nor the counter.

Platform notifications (`GET /platform-notifications`)

GET /platform-notifications (2026-08-24) — not to be confused with the personal "bell" feed above: this is a company-scoped (not per-user) flat log of technical notices from the AISAR platform itself (currently only "channel disconnected", from NotifyChannelDisconnectedJob), replacing the old service-conversation mechanic in chat. It requires the conversation.view permission (the same one the /conversations family uses), the feed window is the last 90 days, newest first, page-based pagination (page/limit, limit 1-50, default 20; a pagination block {page, perPage, total, lastPage}). Rate limit: 120 requests per minute.

A list item: id, kind (the notification type, e.g. channel_disconnected), reason (a human-readable cause, or null), created_at, channel ({id, name, type} or null — null once the channel has since been deleted; the event itself survives), conversation_id and message_id (kept for backward compatibility with notifications inherited from the old mechanic; null on new rows). The feed has no per-item "read" mark — instead, PATCH /me/ui-preferences with aisar_notifications.seen_until (an ISO datetime or null) advances the unseen-cursor consumed by the counters.aisar_notifications key in the Conversations group (GET /conversations/counters).

Endpoints

MethodPath
GET/v1/notifications

The user's notification feed in the active company (Laravel paginator envelope). Params: `filter=unread|all`, `category`, `categories[]`, `q` (2-100 chars), `per_page` (1-100, default 20).

GET/v1/notifications/unread-count

Count of the user's unread notifications in the active company. Response: `{ "count": N, "critical": M, "by_category": { "<category>": n } }` — `critical` counts only unread rows with `severity: "critical"`; `by_category` is the unread count per section, only for n>0 (`{}` when nothing is unread).

POST/v1/notifications/{notification}/read

Mark a single notification read (by its UUID). Response: `{ "status": "ok" }`; 404 if the id is not a UUID, or the notification is missing or owned by another user or company.

POST/v1/notifications/{notification}/unread

Mark a notification unread again (by UUID). Response: `{ "status": "ok" }`; 404 for a foreign or unknown id.

POST/v1/notifications/bulk

Bulk-mark notifications: body `{ "ids": [uuid, ...] (1-100), "action": "read"|"unread" }`. Response: `{ "updated": n }`; ids of other users and companies are silently ignored.

POST/v1/notifications/read-all

Mark all of the user's unread notifications in the active company as read. Response: `{ "status": "ok" }`.

GET/v1/platform-notifications

Company-scoped log of platform notifications (e.g. "channel disconnected") over the last 90 days.

Examples

List unread notifications

Request

bash
curl -X GET "https://api.aisar.app/v1/notifications?filter=unread&per_page=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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
}

Unread count

Request

bash
curl -X GET "https://api.aisar.app/v1/notifications/unread-count" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "count": 3,
  "critical": 1,
  "by_category": { "deals": 2, "tasks": 1 }
}

Mark one notification read

Request

bash
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"

Response

json
{
  "status": "ok"
}

Mark a notification unread

Request

bash
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"

Response

json
{
  "status": "ok"
}

Bulk mark notifications

Request

bash
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"}'

Response

json
{
  "updated": 2
}

Mark all read

Request

bash
curl -X POST "https://api.aisar.app/v1/notifications/read-all" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

json
{
  "status": "ok"
}

Platform notification log

Request

bash
curl -X GET "https://api.aisar.app/v1/platform-notifications?limit=20" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

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 }
  }
}