Skip to main content

Notifications API

Shipped

Notifications are persisted and delivered to registered push devices when a provider is configured. New rows are also available through Supabase Realtime.

In-app notifications. Persisted as rows in notifications and fanned-out in real time via Supabase Realtime.

All endpoints require Authorization: Bearer <supabase-access-token>.

Endpoints​

MethodPathDescription
GET/notificationsList notifications (paginated)
GET/notifications/unread-countUnread count
POST/notifications/readMark a list of IDs as read
DELETE/notifications/:idDelete a notification

List​

GET /notifications?unreadOnly=false&cursor=xxx&limit=20
Authorization: Bearer <token>
QueryTypeDescription
unreadOnlybooleanDefault false
cursor, limitPagination
{
"data": [
{
"id": "uuid",
"type": "group_approved",
"userId": "uuid",
"payload": { "eventId": "uuid", "groupId": "uuid" },
"readAt": null,
"createdAt": "2026-04-01T12:00:00Z"
}
],
"nextCursor": null
}

Notification types​

TypeTrigger
friend_requestSomeone sent you a friend request
friend_request_acceptedA friend request was accepted
event_coauthor_inviteYou were invited as event author
event_coauthor_accepted/refusedA co-author answered
event_applicationA group applied to an event
event_application_approved/rejectedApplication decision
event_invite, event_invite_accepted/refusedEvent invitation activity
chat_createdA chat was created
chat_messageA new message notification
people_likeSomeone liked you
people_matchA mutual like created a match

The payload shape is type-specific and validated server-side.

Unread count​

GET /notifications/unread-count
{ "count": 5 }

Mark as read​

POST /notifications/read
{ "ids": ["uuid1", "uuid2"] }

POST /notifications/read accepts an optional { "ids": ["uuid"] } body. With IDs it marks those caller-owned rows; with no body it marks all rows read. The response is { "updated": 5 }. There is no separate read-all endpoint.

Delete​

DELETE /notifications/:id

Hard-deletes the row (small table, no audit need).

Realtime​

Clients can subscribe to their own notification stream via Supabase Realtime:

supabase
.channel(`user:${userId}:notifications`)
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "notifications", filter: `user_id=eq.${userId}` },
(payload) => addToTray(payload.new),
)
.subscribe();

RLS ensures a user only sees their own rows on the stream.

Implementation notes​

  • Notifications are written by the originating service (e.g. groups.service.ts writes group_approved inside the same Drizzle transaction that flips group status).
  • For higher-volume types (e.g. message), the API may batch or collapse notifications server-side rather than emit one per row.
  • Push delivery uses APNs, FCM, or RuStore when configured; see Push Devices.