Admin — Bookings
Admin endpoints to list, view, approve, reject, assign, complete, and no-show bookings.
Admin — Bookings
Admin-side booking management. Every endpoint here calls requireRole(...),
which first resolves the Better Auth session (UNAUTHENTICATED 401 when there
is none) and then 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;
minimum role receptionist on every endpoint on this page. These paths
carry no /admin prefix — the admin subdomain is the namespace, so the
full URL is e.g. admin.theroyalglow.in/api/bookings. The customer app
exposes its own, distinct /api/bookings on theroyalglow.in (see
Bookings). Money is an integer in paise;
bookingDate filters use YYYY-MM-DD; stored times render as HH:mm:ss.
Timestamps are ISO-8601 UTC.
GET /api/bookings
Lists all bookings across customers (admin view), newest first
(bookingDate then createdAt descending). Each booking is returned with the
customer's name and email and its booking_service snapshot rows (ordered by
displayOrder). Optional query filters narrow the result set; this endpoint
returns the full filtered list and does not paginate (no meta).
Minimum role: receptionist (requireRole('receptionist'))
GET /api/bookings?status=pending&serviceType=salon&date=2026-06-15Query parameters
All optional. Filters are applied with exact equality; omitting a parameter skips that filter.
Prop
Type
Response
{
"success": true,
"data": {
"bookings": [
{
"id": "bk_8f2a1c9e4d",
"bookingNumber": "BK-RS-2606-H-38291",
"branchId": "br_rayasandra01",
"customerId": "usr_abc123",
"status": "pending",
"serviceType": "salon",
"bookingDate": "2026-06-15",
"startTime": "11:00:00",
"endTime": "12:00:00",
"totalAmountPaise": 130000,
"totalDurationMinutes": 60,
"notes": "Prefer a window seat if available.",
"isWalkin": false,
"isMembershipSession": false,
"offerId": null,
"spaMembershipId": null,
"confirmedAt": null,
"completedAt": null,
"cancellationReason": null,
"cancelledAt": null,
"rejectionReason": null,
"rejectedAt": null,
"rescheduleCount": 0,
"createdAt": "2026-06-01T06:15:00.000Z",
"updatedAt": "2026-06-01T06:15:00.000Z",
"customerName": "Aarav Sharma",
"customerEmail": "[email protected]",
"services": [
{
"id": "bs_1",
"bookingId": "bk_8f2a1c9e4d",
"serviceId": "svc_haircut001",
"staffId": null,
"serviceNameSnapshot": "Signature Haircut",
"priceAtBookingPaise": 80000,
"durationMinutes": 45,
"displayOrder": 0
}
]
}
]
}
}Errors
GET /api/bookings/[id]
Returns a single booking (any customer) with the customer's name and email and
its services snapshot rows.
Minimum role: receptionist (requireRole('receptionist'))
GET /api/bookings/bk_8f2a1c9e4dPath parameters
Prop
Type
Response
{
"success": true,
"data": {
"booking": {
"id": "bk_8f2a1c9e4d",
"bookingNumber": "BK-RS-2606-H-38291",
"branchId": "br_rayasandra01",
"customerId": "usr_abc123",
"status": "confirmed",
"serviceType": "salon",
"bookingDate": "2026-06-15",
"startTime": "11:00:00",
"endTime": "12:00:00",
"totalAmountPaise": 130000,
"totalDurationMinutes": 60,
"notes": null,
"isWalkin": false,
"isMembershipSession": false,
"offerId": null,
"spaMembershipId": null,
"confirmedAt": "2026-06-01T07:00:00.000Z",
"completedAt": null,
"cancellationReason": null,
"cancelledAt": null,
"rejectionReason": null,
"rejectedAt": null,
"rescheduleCount": 0,
"createdAt": "2026-06-01T06:15:00.000Z",
"updatedAt": "2026-06-01T07:00:00.000Z",
"customerName": "Aarav Sharma",
"customerEmail": "[email protected]",
"services": [
{
"id": "bs_1",
"bookingId": "bk_8f2a1c9e4d",
"serviceId": "svc_haircut001",
"staffId": "stf_priya01",
"serviceNameSnapshot": "Signature Haircut",
"priceAtBookingPaise": 80000,
"durationMinutes": 45,
"displayOrder": 0
}
]
}
}
}Errors
PATCH /api/bookings/[id]
A single endpoint that performs one of three actions, selected by the action
discriminator. The body is validated by adminBookingActionSchema
(@rgss/types), a discriminated union over action:
approve— only valid on apendingbooking. AssignsstaffIdto every service on the booking, then transitionspending → confirmedand stampsconfirmedAt.reject— only valid on apendingbooking. Transitionspending → rejected, storingrejectionReasonandrejectedAt.assign— (re)assignsstaffIdto every service on the booking, regardless of status.
Minimum role: receptionist (requireRole('receptionist'))
PATCH /api/bookings/bk_8f2a1c9e4d
Content-Type: application/jsonPath parameters
Prop
Type
Request body
Prop
Type
{
"action": "approve",
"staffId": "stf_priya01"
}{
"action": "reject",
"rejectionReason": "No therapist available for the requested slot."
}{
"action": "assign",
"staffId": "stf_priya01"
}Response
The updated booking row is returned.
{
"success": true,
"data": {
"booking": {
"id": "bk_8f2a1c9e4d",
"bookingNumber": "BK-RS-2606-H-38291",
"branchId": "br_rayasandra01",
"customerId": "usr_abc123",
"status": "confirmed",
"serviceType": "salon",
"bookingDate": "2026-06-15",
"startTime": "11:00:00",
"endTime": "12:00:00",
"totalAmountPaise": 130000,
"totalDurationMinutes": 60,
"notes": null,
"isWalkin": false,
"isMembershipSession": false,
"offerId": null,
"spaMembershipId": null,
"confirmedAt": "2026-06-01T07:00:00.000Z",
"completedAt": null,
"cancellationReason": null,
"cancelledAt": null,
"rejectionReason": null,
"rejectedAt": null,
"rescheduleCount": 0,
"createdAt": "2026-06-01T06:15:00.000Z",
"updatedAt": "2026-06-01T07:00:00.000Z"
}
}
}The booking id and the updated booking_service rows are returned.
{
"success": true,
"data": {
"bookingId": "bk_8f2a1c9e4d",
"services": [
{
"id": "bs_1",
"bookingId": "bk_8f2a1c9e4d",
"serviceId": "svc_haircut001",
"staffId": "stf_priya01",
"serviceNameSnapshot": "Signature Haircut",
"priceAtBookingPaise": 80000,
"durationMinutes": 45,
"displayOrder": 0
}
]
}
}Errors
POST /api/bookings/[id]/complete
Completes a booking at the counter: collects the in-person payment method,
transitions the booking to completed, generates a GST-inclusive service
invoice, awards loyalty gems, and (optionally) applies an offer. Only
confirmed or in_progress bookings can be completed.
Minimum role: receptionist (requireRole('receptionist'))
POST /api/bookings/bk_8f2a1c9e4d/complete
Content-Type: application/jsonPath parameters
Prop
Type
Request body
paymentMethod is validated by completeBookingSchema (@rgss/types). The
offer/gems fields are read defensively from the raw body alongside the parsed
payload.
Prop
Type
{
"paymentMethod": "upi",
"offerId": "off_monsoon20"
}Response
Returns the completed booking plus an invoice summary, gems earned, and any discount applied (all amounts in paise).
{
"success": true,
"data": {
"booking": {
"id": "bk_8f2a1c9e4d",
"bookingNumber": "BK-RS-2606-H-38291",
"branchId": "br_rayasandra01",
"customerId": "usr_abc123",
"status": "completed",
"serviceType": "salon",
"bookingDate": "2026-06-15",
"startTime": "11:00:00",
"endTime": "12:00:00",
"totalAmountPaise": 130000,
"totalDurationMinutes": 60,
"notes": null,
"isWalkin": false,
"isMembershipSession": false,
"offerId": null,
"spaMembershipId": null,
"confirmedAt": "2026-06-01T07:00:00.000Z",
"completedAt": "2026-06-15T06:30:00.000Z",
"cancellationReason": null,
"cancelledAt": null,
"rejectionReason": null,
"rejectedAt": null,
"rescheduleCount": 0,
"createdAt": "2026-06-01T06:15:00.000Z",
"updatedAt": "2026-06-15T06:30:00.000Z"
},
"invoice": {
"invoiceNumber": "INV-1-2627-92921",
"totalPaise": 104000,
"gstPaise": 15864
},
"gemsEarned": 10,
"discountPaise": 26000
}
}Gems are earned on the discounted total at 1 gem per ₹100 (floor) and expire
365 days from earn date. The invoice subtotal is the original (pre-discount)
base; the taxable value and GST are split from the discounted total. With no
offerId, discountPaise is 0 and the total is unchanged.
Errors
POST /api/bookings/[id]/noshow
Marks a booking as no_show. Only a confirmed booking can be marked no-show
(intended for use after the appointment window). No request body is required.
No-show tier escalation is handled separately by a background job.
Minimum role: receptionist (requireRole('receptionist'))
POST /api/bookings/bk_8f2a1c9e4d/noshowPath parameters
Prop
Type
Response
{
"success": true,
"data": {
"booking": {
"id": "bk_8f2a1c9e4d",
"bookingNumber": "BK-RS-2606-H-38291",
"branchId": "br_rayasandra01",
"customerId": "usr_abc123",
"status": "no_show",
"serviceType": "salon",
"bookingDate": "2026-06-15",
"startTime": "11:00:00",
"endTime": "12:00:00",
"totalAmountPaise": 130000,
"totalDurationMinutes": 60,
"notes": null,
"isWalkin": false,
"isMembershipSession": false,
"offerId": null,
"spaMembershipId": null,
"confirmedAt": "2026-06-01T07:00:00.000Z",
"completedAt": null,
"cancellationReason": null,
"cancelledAt": null,
"rejectionReason": null,
"rejectedAt": null,
"rescheduleCount": 0,
"createdAt": "2026-06-01T06:15:00.000Z",
"updatedAt": "2026-06-15T07:00:00.000Z"
}
}
}Errors
Was this page helpful?