Notifications endpoints
In-app notifications for the logged-in user: assigned reviews, mentions, publications, etc. Available via classic REST + Server-Sent Events for real-time count.
Endpoint table
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /notifications | Session | Lists notifications and counts. |
| POST | /notifications/mark-read | Session | Marks notifications as read. |
| GET | /notifications/stream | Session | SSE — count updated in real time. |
Scope: these endpoints require a user session (JWT/cookie).
context.user_idandcontext.organization_idmust be filled — pure API keys don't support notifications.
GET /notifications
Returns the user's notifications list for the active organization.
Query params:
limit— 1–200. Default 50.unread_only—truereturns only unread. Defaultfalse.
Response 200 (NotificationListResponse):
{
"items": [
{
"id": "ntf_...",
"user_id": "usr_...",
"organization_id": "org_...",
"kind": "review.created",
"title": "New review on Edge rules project",
"body": "Ana requested your review on 'Refactor sshd'",
"link": "/projects/prj_.../reviews/rev_...",
"read_at": null,
"created_at": "2026-04-24T14:00:00Z"
}
],
"total": 12,
"unread": 3
}
kind— notification type (review.created,review.approved,version.published,api.quota.warning, etc.).read_at—nulluntil read; ISO 8601 once marked.total— total in the returned period.unread— total unread.
POST /notifications/mark-read
Marks specific notifications or all as read.
Request body (NotificationMarkReadRequest):
{ "ids": ["ntf_aaa", "ntf_bbb"] }
ids— list of IDs. If empty/absent, marks all unread notifications for the user in the organization as read.
Response 200: updated NotificationListResponse (same shape as GET /notifications).
GET /notifications/stream (SSE)
Real-time stream that emits the unread count every time it changes.
Request headers:
Accept: text/event-stream
Authorization: Bearer <jwt>
(API keys don't work here — it's a session endpoint.)
Stream format:
event: unread
data: {"unread": 3}
: keep-alive
event: unread
data: {"unread": 4}
: keep-alive
- The first
event: unreadarrives immediately on connect (seeds client state). : keep-aliveis an SSE comment to prevent proxies from closing the connection.- The next
event: unreadonly appears when the count changes.
Limits
- Concurrent connections per user are limited. If exceeded,
429 sse_connection_limit_exceededwithRetry-After. Reuse the open connection. - The server uses internal polling (5 s) to detect changes — maximum notification latency is ~5 s.
Example (browser)
const es = new EventSource("/api/v1/notifications/stream", { withCredentials: true })
es.addEventListener("unread", (ev) => {
const { unread } = JSON.parse(ev.data)
document.getElementById("badge").textContent = String(unread)
})
es.onerror = (err) => {
console.error("SSE failed", err)
// browser EventSource auto-reconnects; it respects Retry-After on 429.
}
Example (curl)
curl -N "$RF_BASE/notifications/stream" \
-H "Accept: text/event-stream" \
-H "Authorization: Bearer $JWT"
(-N disables curl buffering so we can see events arriving.)
Shared structures
NotificationRecord
{
"id": "string",
"user_id": "string",
"organization_id": "string",
"kind": "string",
"title": "string",
"body": "string",
"link": "string|null",
"read_at": "ISO 8601|null",
"created_at": "ISO 8601"
}
NotificationListResponse
{
"items": [ "NotificationRecord[]" ],
"total": 0,
"unread": 0
}