Type: reference · Imported reference (adopted from the clarity backend developer docs, hardened against the mounted routes)
Persistent, RBAC-aware notifications delivered to the desktop app (native OS toast + in-app banner) and to LAN browser sessions over Server-Sent Events. See Notifications Engine for the internal model, transports, and dispatcher; this page is the HTTP contract.
All routes are under /exactapi/notifications. The standard list / get / count / findOne / bulk / update / delete verbs are not custom — they come from the generic entity CRUD contract (notifications is an ordinary metadata entity with a meta_data BLOB). Only the four routes below are custom.
Applies to: whole platform · monitor is the only wired producer today.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /exactapi/notifications |
Admin (in-handler check) | Validated create; delegates to the notification service. |
| GET | /exactapi/notifications/stream |
Header or ?access_token= |
SSE live feed + replay of undelivered rows. |
| POST | /exactapi/notifications/{id}/read |
Any logged-in user | Mark read (idempotent, visibility-guarded). |
| GET | /exactapi/notifications/unread-count |
Any logged-in user | Bell-badge count. |
Admin gate detail: the create filter uses
with_auth(any logged-in user); the admin restriction is the in-handlerclaims.role != "admin"check, not the filter. See the engine page.
/exactapi/notifications — sendCreate one notification. Admin only.
Request body — required: severity, source, title, body. Optional: userId, orgsId, sitesId, unitsId, link, redirect, payload, sourceRef, expiresAt.
{
"userId": null,
"orgsId": 1,
"unitsId": 7,
"severity": "error",
"source": "admin",
"title": "Generator test scheduled",
"body": "Generator test will run at 16:00 UTC today.",
"link": "/schedules/42",
"redirect": true,
"payload": { "scheduleId": 42 },
"sourceRef": "admin:schedule-42",
"expiresAt": null
}
severity ∈ info | warn | error | critical.source ∈ monitor | process_manager | licensing | backup | system | admin | custom.createdAt, readAt (null), deliveredAt. userId = null = broadcast (scoped by orgsId/sitesId/unitsId, or system-wide if all null).title ≤ 120 B, body ≤ 2048 B, payload ≤ 16384 B; redirect: true requires a link; expiresAt must not be in the past.Responses:
| Status | Meaning |
|---|---|
| 201 | Created — returns the persisted notification. |
| 400 | Validation failed (bad severity/source, oversize field, redirect without link, past expiresAt). |
| 401 | Missing/invalid token. |
| 403 | Not an admin. |
/exactapi/notifications — list (generic CRUD)Served by the entity CRUD contract, not a custom handler. Use the standard LoopBack filter:
GET /exactapi/notifications?filter={"where":{"userId":"alice@acme-power.example"},"order":"id DESC","limit":50}
RBAC scoping applies (see below). For the unread badge use the dedicated count route rather than a filtered list.
> TODO-VERIFY:the upstream design doc lists first-classseverity/source/unread/from/toquery params and an{items,total,limit,offset}envelope for list — these are not confirmed on the mounted generic-CRUD route, which returns a bare array and takesfilter=. Treat the design-doc list contract as aspirational until verified.
/exactapi/notifications/{id}/read — mark readSets readAt to now. Idempotent (second call is a no-op; readAt not overwritten). Visibility-guarded — a 404 is returned for both "does not exist" and "not visible to you" (existence is not leaked).
| Status | Body |
|---|---|
| 200 | { "id": 4821, "readAt": 1761627920000 } |
| 404 | Not found / not visible. |
/exactapi/notifications/unread-count — bell badgeCount of unread (readAt IS NULL) notifications visible to the caller.
{ "count": 7 }
/exactapi/notifications/stream — SSE live feedServer-Sent Events. On connect the server replays every undelivered (deliveredAt IS NULL) notification visible to the caller (oldest first, LIMIT 100), emits a replay-complete event, then switches to live broadcast.
Auth: Authorization: Bearer <token> or ?access_token=<token> (browsers must use the query param — EventSource cannot set headers).
Response headers: Content-Type: text/event-stream, X-Accel-Buffering: no.
Wire format — one event per notification, plus a : keepalive comment every 25 s:
event: notification
id: 4821
data: {"id":4821,"severity":"error","source":"monitor","title":"...","unitsId":7,"payload":{...},"createdAt":1761627848123,"readAt":null,"deliveredAt":null}
event: replay-complete
data: {"replayed":17}
(trailing blank line required by the SSE spec). Clients resume from the last received id via the standard Last-Event-ID mechanism.
const es = new EventSource('/exactapi/notifications/stream?access_token=' + token);
es.addEventListener('notification', (ev) => { const n = JSON.parse(ev.data); /* … */ });
es.addEventListener('replay-complete', (ev) => { /* "showing N older" */ });
Every read route (list, get, mark-read, stream, unread-count) enforces the same rule. Admin sees everything. A non-admin sees a row when:
userId equals their own email (addressed to me), oruserId is null and the row's orgsId/sitesId/unitsId is in the caller's granted scope (broadcast), orA user-addressed row (userId set) is visible only to that user and admins, even if scope columns are also set. See Notifications Engine → RBAC.
Known gap: monitor-produced notifications set only
unitsId(orgsId/sitesIdare null), so their visibility relies on unit scope or the admin role.
Read once at startup (clarity.properties; restart to apply):
clarity.notifications.retention_days — default 30; 0 disables the purge. Unread rows are always kept.clarity.notifications.retention_interval_seconds — default 86400.
> TODO-VERIFY:the upstream design doc also listsclarity.notifications.enabled,.sse.enabled,.sse.max_subscribers_per_user, and.min_severity. Only the two retention keys above are confirmed loaded inconfig.rstoday — treat the others as design-stage.
Thin client wrappers (auth + JSON + SSE + ring buffer) for Python and TypeScript (core, React, Angular) — see Notification SDKs.
Adopted from clarity backend developer docs (
docs/developer/NOTIFICATIONS_API.md), hardened against the mounted routes.
Primary handlers:clarity:backend/src-tauri/src/notifications/routes.rs,clarity:backend/src-tauri/src/notifications/sse.rs.
Last updated: 2026-07-18 from clarity@6800acc