Customers & CRM
Admin CRM endpoints covering the customer directory, individual profiles, free-text notes, and the customer-tag system. Every endpoint callsrequireRole(...), which resolves the Better Auth session
(UNAUTHENTICATED 401 when there is none) and checks the caller’s role against
the hierarchy customer < staff < receptionist < manager < owner < developer
(FORBIDDEN 403 when the level is too low).
Base URL:
admin.theroyalglow.in · Auth: Better Auth session. These
paths carry no /admin prefix — the admin subdomain is the namespace, so
the full URL is e.g. admin.theroyalglow.in/api/customers. Most
endpoints require receptionist; profile overrides
(PATCH /api/customers/[id]) and tag creation (POST /api/tags)
require manager. Money is an integer in paise; dateOfBirth is
YYYY-MM-DD; timestamps are ISO-8601 UTC. A “customer” id is the underlying
user id.GET /api/customers
Paginated, searchable, sortable customer directory. Returnsuser rows with
role customer that have a customer_profile, each enriched with KPIs, the
loyalty gems balance (LEFT JOined — null when no account exists), and tag
chips. This endpoint paginates and includes meta.
Minimum role: receptionist (requireRole('receptionist'))
Query parameters
Validated bycustomerListQuerySchema (@rgss/types).
Response
meta carries pagination. totalPages is max(1, ceil(totalCount / pageSize)).
Errors
GET /api/customers/[id]
Returns a single customer profile: user identity pluscustomer_profile KPIs,
the gems balance (null when no loyalty account), and tag chips.
Minimum role: receptionist (requireRole('receptionist'))
Path parameters
Response
Errors
PATCH /api/customers/[id]
Manager-level override of profile gates — e.g. reset the no-show count or toggle the booking-approval requirement. Both fields are optional; an empty body is a no-op that returns the current profile. Minimum role:manager (requireRole('manager'))
Path parameters
Request body
Validated by an inline Zod schema in the handler.Response
Returns the updatedcustomer_profile row.
Errors
POST /api/customers/[id]/notes
Adds a free-text note to a customer, optionally linked to a booking. The author (the signed-in user) and a timestamp are persisted automatically. Minimum role:receptionist (requireRole('receptionist'))
Path parameters
Request body
Validated byaddCustomerNoteSchema (@rgss/types).
Response
Returns201 Created with the created note row.
Errors
POST /api/customers/[id]/tags
Assigns an existing tag to a customer. Idempotent: re-assigning a tag the customer already has is a silent no-op (the composite primary key absorbs the conflict). The assigning user is recorded. Minimum role:receptionist (requireRole('receptionist'))
Path parameters
Request body
Validated byassignTagSchema (@rgss/types).
Response
Returns201 Created.
Errors
DELETE /api/customers/[id]/tags/[tagId]
Removes a tag assignment from a customer. No-op if the assignment does not exist (still returns success). Minimum role:receptionist (requireRole('receptionist'))
Path parameters
Response
Errors
GET /api/tags
Lists all customer tags, alphabetical by name — used to populate the tag picker. Minimum role:receptionist (requireRole('receptionist'))
Response
Errors
POST /api/tags
Creates a new customer tag. Theslug is derived from name in the query layer
(lowercased, whitespace runs collapsed to hyphens).
Minimum role: manager (requireRole('manager'))
Request body
Validated bycreateTagSchema (@rgss/types).
Response
Returns201 Created with the created tag row.