Routes mounted under /aif/api/notifications. Notifications are delivered both in-app (displayed by the NotificationBell component) and, when enabled, by email. Each user controls their own delivery preferences.
All endpoints require authentication. Notifications are strictly scoped to the authenticated user; there is no admin override for reading other users' notifications.
The following event types are emitted by other endpoints:
| Type | Emitted by |
|---|---|
pipeline_complete |
Pipeline orchestrator on terminal state |
review_approved |
POST /review/:toolId/decision (decision = approved) |
review_changes_requested |
POST /review/:toolId/decision (decision = changes_requested) |
track_override |
POST /review/:toolId/track-override |
comment |
POST /review/:toolId/notes |
tool_activated |
POST /review/:toolId/activate or /self-certify |
Returns the caller's notifications, newest first, together with the current unread count.
Auth: Authenticated.
Query parameters:
| Name | Type | Default | Notes |
|---|---|---|---|
limit |
integer | 30 | Max 100 |
offset |
integer | 0 | — |
unread |
string | — | When "true", returns only unread notifications |
Response 200 OK:
{
"notifications": [
{
"id": "9e4a...",
"user_id": 42,
"tool_id": "0c3f...",
"tool_name": "Student Dashboard",
"type": "review_approved",
"title": "\"Student Dashboard\" has been approved",
"body": "Findings addressed; ready to activate.",
"link": "#/tool/0c3f...",
"read": false,
"created_at": "2026-04-14T21:15:00Z"
}
],
"unread_count": 3
}Errors:
401 Unauthorized—Authentication required
Marks one or more notifications as read. The server accepts either a list of UUIDs or the literal string "all" to mark every unread notification for the caller.
Auth: Authenticated. CSRF: Required.
Request body:
| Field | Type | Constraints |
|---|---|---|
ids |
"all" or UUID array |
Array must contain 1+ valid UUIDs |
{ "ids": ["9e4a...", "4c21..."] }or
{ "ids": "all" }Response 200 OK:
{ "success": true, "unread_count": 1 }Errors:
400 Bad Request— validation failure (ids: Invalid input)401 Unauthorized—Authentication required
Returns the caller's email address and delivery toggles.
Auth: Authenticated.
Response 200 OK:
{
"email": "jdoe@example.edu",
"notify_email": true,
"notify_in_app": true
}email may be null if the user has not configured one and no default could be derived from their netid + institution domain.
Updates the caller's notification preferences. At least one of notify_email or notify_in_app must be present; partial updates are supported.
Auth: Authenticated. CSRF: Required.
Request body:
| Field | Type | Notes |
|---|---|---|
notify_email |
boolean | Enable or disable email delivery |
notify_in_app |
boolean | Enable or disable in-app delivery |
{ "notify_email": false }Response 200 OK:
{ "success": true }Errors:
400 Bad Request—No valid preferences provided(neither field supplied)400 Bad Request— validation failure (notify_email: Expected boolean)401 Unauthorized—Authentication required
Updates the caller's email address. Email is used solely for notification delivery; it is not an identity credential. Passing an empty string clears the stored address.
Auth: Authenticated. CSRF: Required.
Request body:
| Field | Type | Constraints |
|---|---|---|
email |
string | Valid email address, or empty string to clear |
{ "email": "jane.doe@example.edu" }Response 200 OK:
{ "success": true }Errors:
400 Bad Request—email: Invalid email401 Unauthorized—Authentication required
- A notification is stored in the
notificationstable whenevernotify()ornotifyRole()fires, regardless of the user'snotify_in_apppreference. The preference controls whether the bell surfaces the notification in the UI. - Email delivery is attempted only when the user has
notify_email = true, has a non-nullemail, and SMTP is configured on the server. - Email delivery failures are logged but do not block the triggering operation.
- Read notifications older than 30 days are pruned by the data retention job (see admin.md).
- review.md — source of most notifications
- admin.md — retention and cleanup
- overview.md — CSRF rules for state-changing requests