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 tosession.user.id.
Minimum role: customer (requireSession)
Query parameters
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.
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
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
No active session.
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
Unexpected/transient failure.
retryable: true.PATCH /api/notifications
Marks the caller’s notifications as read. Withids, 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)
Request body
The body is optional and validated bymarkReadSchema (@rgss/types). A missing
or empty body is treated as “mark all read”.
- Mark specific
- Mark all read
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
No active session.
VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
Body fails
markReadSchema (e.g. ids is not an array of non-empty strings).
details carries field-level errors.INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
Unexpected/transient failure.
retryable: true.POST /api/push/subscribe
Stores a Web Push subscription for the caller. The subscription is upserted byendpoint 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)
Request body
Validated bypushSubscribeSchema (@rgss/types) — the shape a browser
PushManager subscription serialises to.
Response
Returns201 Created.
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
No active session.
VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
Body fails
pushSubscribeSchema — e.g. endpoint is not a valid URL, or a key
is missing/empty. details carries field-level errors.INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
Unexpected/transient failure.
retryable: true.DELETE /api/push/subscribe
Deactivates the caller’s push subscription for a given endpoint (soft unsubscribe — setsisActive 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)
Request body
Not a Zod schema — the handler requires a non-empty stringendpoint directly.
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
No active session.
VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
endpoint is missing or not a non-empty string (raised as badRequest).INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
Unexpected/transient failure.
retryable: true.POST /api/ably/token
Issues an Ably token request scoped to the caller’s own channels. The token is always grantedsubscribe 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)
Response
The payload is the Ably token request object produced byclient.auth.createTokenRequest(...) — passed straight through as data. Its
fields are defined by Ably; the example below is illustrative.
receptionist+), capability also includes
"admin:*":["subscribe"].
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
No active session.
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
Unexpected/transient failure.
retryable: true.