Notifications API
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
| Method | Path | Description |
|---|---|---|
| GET | /notifications | List notifications (paginated) |
| GET | /notifications/unread-count | Unread count |
| POST | /notifications/read | Mark a list of IDs as read |
| DELETE | /notifications/:id | Delete a notification |
List
GET /notifications?unreadOnly=false&cursor=xxx&limit=20
Authorization: Bearer <token>
| Query | Type | Description |
|---|---|---|
unreadOnly | boolean | Default false |
cursor, limit | Pagination |
{
"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
| Type | Trigger |
|---|---|
friend_request | Someone sent you a friend request |
friend_request_accepted | A friend request was accepted |
event_coauthor_invite | You were invited as event author |
event_coauthor_accepted/refused | A co-author answered |
event_application | A group applied to an event |
event_application_approved/rejected | Application decision |
event_invite, event_invite_accepted/refused | Event invitation activity |
chat_created | A chat was created |
chat_message | A new message notification |
people_like | Someone liked you |
people_match | A 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.tswritesgroup_approvedinside 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.