Skip to main content
SPA memberships are hour-based: a customer buys a tier (e.g. Silver 8hrs, Gold 15hrs) valid for a fixed number of days, and each SPA session deducts its duration from the remaining hours. There is one customer-facing read endpoint (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.
Base URLs: the customer read endpoint is 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

No active session.
Unexpected/transient failure. 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

No active session.
Session role is below receptionist.
Unexpected/transient failure. 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

No active session.
Session role is below receptionist.
Unexpected/transient failure. 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 by createMembershipSchema (@rgss/types).

Response

Returns 201 Created with the created membership and its purchase invoice.

Errors

No active session.
Session role is below receptionist.
Body fails createMembershipSchema, or the selected tier does not exist / is inactive.
The customer already has an active membership.
The primary branch could not be resolved.
Unexpected/transient failure. 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

No active session.
Session role is below receptionist.
No membership with that id.
Unexpected/transient failure. 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 by recordSessionSchema (@rgss/types).

Response

Returns 201 Created with the created booking, the ₹0 session invoice, and the membership’s remaining minutes after the deduction.

Errors

No active session.
Session role is below receptionist.
Body fails recordSessionSchema, or one or more selected services do not exist.
No membership with that id, or the primary branch could not be resolved.
The membership is not active or is past its expiry.
The requested duration exceeds the remaining membership hours.
Unexpected/transient failure. 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 by cancelMembershipSchema (@rgss/types).

Response

Returns the updated membership row.

Errors

No active session.
Session role is below manager.
Body fails cancelMembershipSchema (missing or over-long reason).
No membership with that id.
Unexpected/transient failure. retryable: true.
Last modified on August 29, 2026