Notifications & Realtime
In-app notification feed, Web Push subscription management, and Ably realtime token requests.
Notifications & Realtime
Endpoints for the in-app notification feed, the browser Web Push subscription lifecycle, and issuing scoped Ably token requests for live updates.
Base URL: https://theroyalglow.in · Auth: every endpoint here calls
requireSession, so a valid Better Auth session cookie is required. All reads
and writes are scoped to session.user.id — a caller can only ever see or
modify their own notifications and subscriptions. The role hierarchy is
customer < staff < receptionist < manager < owner < developer. The admin app
exposes its own counterparts on admin.theroyalglow.in (e.g.
admin.theroyalglow.in/api/notifications, .../api/ably/token); this page
documents the customer surface on theroyalglow.in.
GET /api/notifications
Returns the signed-in user's notification feed (newest first, paginated) plus
the count of their unread notifications. Strictly scoped to session.user.id.
Minimum role: customer (requireSession)
GET /api/notifications?page=1&pageSize=20Query parameters
Prop
Type
Response
data.notifications are the full notification rows, newest first.
data.unreadCount is the number of the caller's notifications with readAt
still null. meta carries page and totalPages — there is no separate count
query, so totalPages is page + 1 whenever a full page is returned (there may
be more), otherwise page.
{
"success": true,
"data": {
"notifications": [
{
"id": "ntf_7d2c9a1f3b",
"userId": "usr_abc123",
"bookingId": "bk_8f2a1c9e4d",
"type": "booking_confirmed",
"title": "Your booking is confirmed",
"body": "Booking BK-RS-2606-H-38291 is confirmed for 15/06/2026 at 11:00.",
"channel": "push",
"status": "sent",
"sentAt": "2026-06-01T07:00:05.000Z",
"readAt": null,
"createdAt": "2026-06-01T07:00:00.000Z"
}
],
"unreadCount": 1
},
"meta": {
"page": 1,
"totalPages": 1
}
}type is one of the notification_type enum values (e.g. reminder_24h,
reminder_1h, booking_confirmed, booking_rescheduled, booking_cancelled,
booking_rejected, membership_created, gems_expiry_7d, gems_expired, …).
channel is push or email; status is pending, sent, or failed.
Errors
PATCH /api/notifications
Marks the caller's notifications as read. With ids, only those notifications
are marked; omit ids (or send an empty/missing body) to mark all of the
caller's unread notifications read. Scoped to session.user.id, so one user can
never mark another user's notifications.
Minimum role: customer (requireSession)
PATCH /api/notifications
Content-Type: application/jsonRequest body
The body is optional and validated by markReadSchema (@rgss/types). A missing
or empty body is treated as "mark all read".
Prop
Type
{
"ids": ["ntf_7d2c9a1f3b", "ntf_4e8b0c2d6a"]
}Empty or omitted body marks all the caller's unread notifications read.
{}Response
{
"success": true,
"data": {
"ok": true
}
}Errors
POST /api/push/subscribe
Stores a Web Push subscription for the caller. The subscription is upserted by
endpoint and bound to session.user.id (re-binding and reactivating an
existing row for that endpoint, or inserting a fresh one).
Minimum role: customer (requireSession)
POST /api/push/subscribe
Content-Type: application/jsonRequest body
Validated by pushSubscribeSchema (@rgss/types) — the shape a browser
PushManager subscription serialises to.
Prop
Type
{
"endpoint": "https://fcm.googleapis.com/fcm/send/abc123",
"keys": {
"p256dh": "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM",
"auth": "tBHItJI5svbpez7KI4CCXg"
}
}Response
Returns 201 Created.
{
"success": true,
"data": {
"ok": true
}
}Errors
DELETE /api/push/subscribe
Deactivates the caller's push subscription for a given endpoint (soft
unsubscribe — sets isActive to false). The endpoint is read from the JSON body.
Scoped to session.user.id, so one user can never deactivate another's
subscription.
Minimum role: customer (requireSession)
DELETE /api/push/subscribe
Content-Type: application/jsonRequest body
Not a Zod schema — the handler requires a non-empty string endpoint directly.
Prop
Type
{
"endpoint": "https://fcm.googleapis.com/fcm/send/abc123"
}Response
{
"success": true,
"data": {
"ok": true
}
}Errors
POST /api/ably/token
Issues an Ably token request scoped to the caller's own
channels. The token is always granted subscribe on customer:{userId}:*;
callers whose role is receptionist or above additionally receive subscribe
on admin:*. The clientId on the token request is the caller's user id.
Minimum role: customer (requireSession)
Realtime degrades gracefully. When ABLY_PRIVATE_KEY is not configured
(or the optional ably module is unavailable), the server helper returns
null and this endpoint answers 503 SERVICE_UNAVAILABLE so the client
can fall back to polling.
POST /api/ably/tokenThis endpoint takes no request body.
Response
The payload is the Ably token request object produced by
client.auth.createTokenRequest(...) — passed straight through as data. Its
fields are defined by Ably; the example below is illustrative.
{
"success": true,
"data": {
"keyName": "abcDef.ghiJkl",
"clientId": "usr_abc123",
"ttl": 3600000,
"capability": "{\"customer:usr_abc123:*\":[\"subscribe\"]}",
"timestamp": 1717225200000,
"nonce": "8b1c0a3e7f2d4c6a",
"mac": "Base64HmacSignature=="
}
}For an admin caller (receptionist+), capability also includes
"admin:*":["subscribe"].
Errors
Was this page helpful?