Skip to main content

Customers & CRM

Admin CRM endpoints covering the customer directory, individual profiles, free-text notes, and the customer-tag system. Every endpoint calls requireRole(...), 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. Returns user 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 by customerListQuerySchema (@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 plus customer_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 updated customer_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 by addCustomerNoteSchema (@rgss/types).

Response

Returns 201 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 by assignTagSchema (@rgss/types).

Response

Returns 201 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. The slug is derived from name in the query layer (lowercased, whitespace runs collapsed to hyphens). Minimum role: manager (requireRole('manager'))

Request body

Validated by createTagSchema (@rgss/types).

Response

Returns 201 Created with the created tag row.

Errors

Last modified on June 29, 2026