theroyalglow.in/api/membership, singular); everything else is an admin
endpoint served from admin.theroyalglow.in (e.g.
admin.theroyalglow.in/api/memberships, plural) and is role-gated.
https://theroyalglow.in; the
admin endpoints are served from https://admin.theroyalglow.in with no
/api/admin/ segment — the subdomain is the namespace (e.g.
admin.theroyalglow.in/api/memberships, .../api/membership-tiers). Roles
are hierarchical: customer < staff <
receptionist < manager < owner < developer. Money is an integer in
paise (₹10,000 = 1000000). Hours are stored as minutes — the
*HoursMinutes fields hold minutes, so Silver 8hrs = 480 and Gold 15hrs =
900. Dates in request bodies are YYYY-MM-DD; timestamps in responses are
UTC ISO strings. Membership status is one of active, expired, cancelled.
Membership numbers use the format RG-MEM-{YY}-{branchNumber}-{5 random}.GET theroyalglow.in/api/membership
Returns the signed-in customer’s own membership data: the single active
membership (if any), an array of past (expired/cancelled) memberships, and the
sessions history for the active membership. Strictly scoped to the
authenticated customer — it never exposes another customer’s data. When there is
no active membership, active is null and sessions is an empty array.
Minimum role: customer (requireSession)
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.GET admin.theroyalglow.in/api/membership-tiers
Lists active membership tiers, ordered by displayOrder — used to populate the
create-membership form / tier picker. Hours and validity prefill the create
form but remain overridable.
Minimum role: receptionist (requireRole('receptionist'))
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
receptionist.INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.GET admin.theroyalglow.in/api/memberships
Lists memberships, newest first, each flattened with the customer name and tier
name. Optional query filters narrow by tier id and/or status. The payload is the
array itself (no wrapping object).
Minimum role: receptionist (requireRole('receptionist'))
Query parameters
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
receptionist.INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.POST admin.theroyalglow.in/api/memberships
Creates a SPA membership and its membership_purchase invoice atomically. The
handler enforces one active membership per customer (a friendly 409 ahead of the
DB partial-unique index), resolves the tier for its name snapshot, computes the
expiry from startDate + validityDays, and splits the GST-inclusive price into
base + GST on the invoice. No gems are earned on purchase. A best-effort expiry
notice job is scheduled but never affects the response.
Minimum role: receptionist (requireRole('receptionist'))
Request body
Validated bycreateMembershipSchema (@rgss/types).
Response
Returns201 Created with the created membership and its purchase invoice.
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
receptionist.VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
createMembershipSchema, or the selected tier does not exist / is
inactive.MEMBERSHIP_ALREADY_ACTIVE — 409
MEMBERSHIP_ALREADY_ACTIVE — 409
NOT_FOUND — 404
NOT_FOUND — 404
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.GET admin.theroyalglow.in/api/memberships/[id]
Returns a single membership enriched with customerName, customerEmail, the
full tier object, and its completed-session history (sessions, each with its
booking_service snapshot rows).
Minimum role: receptionist (requireRole('receptionist'))
Path parameters
Response
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
receptionist.NOT_FOUND — 404
NOT_FOUND — 404
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.POST admin.theroyalglow.in/api/memberships/[id]/sessions
Records a SPA session against a membership. Each service line deducts its
duration from the remaining hours. The handler guards membership state and
remaining hours before any writes, validates that every service id exists,
snapshots service and staff names, then atomically creates a completed ₹0
booking flagged as a membership session (booking number suffixed -M), its
booking_service rows, a ₹0 membership_session invoice (zero gems), and
increments the membership’s used minutes. bookingDate defaults to today (IST).
Minimum role: receptionist (requireRole('receptionist'))
Path parameters
Request body
Validated byrecordSessionSchema (@rgss/types).
Response
Returns201 Created with the created booking, the ₹0 session invoice, and the
membership’s remaining minutes after the deduction.
Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
receptionist.VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
recordSessionSchema, or one or more selected services do not exist.NOT_FOUND — 404
NOT_FOUND — 404
MEMBERSHIP_EXPIRED — 409
MEMBERSHIP_EXPIRED — 409
active or is past its expiry.MEMBERSHIP_INSUFFICIENT_HOURS — 409
MEMBERSHIP_INSUFFICIENT_HOURS — 409
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.POST admin.theroyalglow.in/api/memberships/[id]/cancel
Cancels a membership: sets status to cancelled and appends the reason to the
membership’s notes. This is the only membership endpoint that requires
manager.
Minimum role: manager (requireRole('manager'))
Path parameters
Request body
Validated bycancelMembershipSchema (@rgss/types).
Response
Returns the updated membership row.Errors
UNAUTHENTICATED — 401
UNAUTHENTICATED — 401
FORBIDDEN — 403
FORBIDDEN — 403
manager.VALIDATION_ERROR — 400
VALIDATION_ERROR — 400
cancelMembershipSchema (missing or over-long reason).NOT_FOUND — 404
NOT_FOUND — 404
INTERNAL_ERROR — 500
INTERNAL_ERROR — 500
retryable: true.