Bookings - Public & Guest
Base path: /api/v1/bookings
Used by: cms-booki-web-app · Role: guest (public)
organizationId: Resolved by tenant middleware (resolveRequiredTenant) — from x-tenant-slug header (auto-injected by $api plugin from subdomain) or from the :organizationId route param
Public booking endpoints allow guests to create and verify bookings without logging in. All requests are tenant-scoped via subdomain or header.
POST /api/v1/bookings/type/:type — Create Initial Booking (Tenant Subdomain)
Auth: Public (resolveRequiredTenant)
Used by: cms-booki-web-app · Role: guest
organizationId: From x-tenant-slug header or subdomain (tenant middleware)
Description: Create a new booking on the current tenant's services as a guest. If guest email matches an existing customer, that customer account is used instead.
Request
POST /api/v1/bookings/type/haircut
Header: X-Tenant-Slug: janes-salon (or via subdomain)
Body:
{
"packageId": "507f1f77bcf86cd799439031",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"firstName": "John",
"lastName": "Smith",
"email": "john@example.com",
"phone": "09161234567",
"preferredPaymentMethod": "cash",
"password": ""
}
Fields:
packageId(string, required): Service package ID (24-hex)bookingDate(string, required): FormatYYYY-MM-DD(future date only)bookingTime(string, required): FormatHH:MM(24-hour, future time only)firstName(string, required): Guest's first name (max 255)middleName(string, optional): Guest's middle name (max 255)lastName(string, required): Guest's last name (max 255)email(string, required): Valid email (lowercase)phone(string, required): Phone number (10-20 chars)preferredPaymentMethod(string, optional):cashormaya(default: business preference)password(string, optional): If set, creates guest as registered customer
Response (201 Created)
{
"booking": {
"_id": "507f1f77bcf86cd799439041",
"organizationId": "507f191e810c19729de860ea",
"packageId": "507f1f77bcf86cd799439031",
"customerId": "507f1f77bcf86cd799439051",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"status": "pending",
"preferredPaymentMethod": "cash",
"createdAt": "2026-04-01T09:15:00Z",
"verificationToken": "VERIFY_TOKEN_ABC123..."
},
"message": "Booking created. Please verify via email link."
}
Error Responses
400 Bad Request — Past date:
{
"statusCode": 400,
"message": "Please select a valid date for your booking. Past dates are not available."
}
400 Bad Request — Invalid package:
{
"statusCode": 400,
"message": "Package not found"
}
404 Not Found — Tenant not found:
{
"statusCode": 404,
"message": "Organization not found"
}
cURL Example
curl -X POST http://localhost:4001/api/v1/bookings/type/haircut \
-H "Content-Type: application/json" \
-H "X-Tenant-Slug: janes-salon" \
-d '{
"packageId": "507f1f77bcf86cd799439031",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"firstName": "John",
"lastName": "Smith",
"email": "john@example.com",
"phone": "09161234567",
"preferredPaymentMethod": "cash"
}'
POST /api/v1/bookings/organizations/:organizationId/type/:type — Create Booking (Explicit Org ID)
Auth: Public (resolveRequiredTenant + resolveRequiredBranch)
Used by: cms-booki-web-app (when subdomain is not available)
organizationId: From the :organizationId route parameter — no X-Tenant-Slug header needed
branchId: Resolved from X-Branch-Slug header — required for branch resolution
Description: Create a booking by explicitly providing the organization ID in the URL path. Use this when a tenant subdomain is unavailable (e.g., direct API integrations, QR codes, or link-based booking flows).
Request
POST /api/v1/bookings/organizations/507f191e810c19729de860ea/type/haircut
Header: X-Branch-Slug: main-branch
Body: same fields as /type/:type above.
Response (201 Created)
Same as above.
cURL Example
curl -X POST "http://localhost:4001/api/v1/bookings/organizations/507f191e810c19729de860ea/type/haircut" \
-H "Content-Type: application/json" \
-H "X-Branch-Slug: main-branch" \
-d '{
"packageId": "507f1f77bcf86cd799439031",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"firstName": "John",
"lastName": "Smith",
"email": "john@example.com",
"phone": "09161234567"
}'
Error Responses
422 Unprocessable Entity — Missing or unresolvable branch:
{
"statusCode": 422,
"message": "Branch could not be determined. Please ensure you access via a valid branch URL."
}
PUT /api/v1/bookings/:id/verify — Verify Booking
Auth: Public
Used by: cms-booki-web-app (email verification link) · Role: guest
organizationId: Not required (booking ID is sufficient)
Description: Verify a booking from the email confirmation link. No request body is required — the booking is identified by the :id path parameter.
Request
PUT /api/v1/bookings/507f1f77bcf86cd799439041/verify
Response (200 OK)
{
"booking": {
"_id": "507f1f77bcf86cd799439041",
"organizationId": "507f191e810c19729de860ea",
"status": "confirmed",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"verifiedAt": "2026-04-01T09:30:00Z"
},
"message": "Booking verified successfully"
}
Error Responses
400 Bad Request — Invalid token:
{
"statusCode": 400,
"message": "Invalid or expired verification token"
}
cURL Example
curl -X PUT "http://localhost:4001/api/v1/bookings/507f1f77bcf86cd799439041/verify"
GET /api/v1/bookings/:id — Get Booking by ID
Auth: Public
Used by: cms-booki-web-app · Role: guest
organizationId: Not required (booking ID is sufficient)
Description: Retrieve booking details by ID (no authentication needed).
Request
GET /api/v1/bookings/507f1f77bcf86cd799439041
Response (200 OK)
{
"_id": "507f1f77bcf86cd799439041",
"organizationId": "507f191e810c19729de860ea",
"packageId": "507f1f77bcf86cd799439031",
"customerId": "507f1f77bcf86cd799439051",
"bookingDate": "2026-04-10",
"bookingTime": "14:30",
"status": "confirmed",
"preferredPaymentMethod": "cash",
"createdAt": "2026-04-01T09:15:00Z",
"verifiedAt": "2026-04-01T09:30:00Z"
}
Error Responses
404 Not Found — Booking doesn't exist:
{
"statusCode": 404,
"message": "Booking not found"
}
cURL Example
curl -X GET http://localhost:4001/api/v1/bookings/507f1f77bcf86cd799439041
GET /api/v1/bookings/fully-booked-slots — List Fully Booked Timeslots
Auth: Public (resolveRequiredTenant)
Used by: cms-booki-web-app · Role: guest
organizationId: From x-tenant-slug header or subdomain (tenant middleware)
Description: Get all timeslots that are fully booked for the tenant.
Request
GET /api/v1/bookings/fully-booked-slots
Header: X-Tenant-Slug: janes-salon
Response (200 OK)
{
"fullyBookedSlots": [
{
"date": "2026-04-10",
"time": "14:30",
"packageId": "507f1f77bcf86cd799439031"
},
{
"date": "2026-04-10",
"time": "15:00",
"packageId": "507f1f77bcf86cd799439031"
}
],
"count": 2
}
cURL Example
curl -X GET "http://localhost:4001/api/v1/bookings/fully-booked-slots" \
-H "X-Tenant-Slug: janes-salon"
Notes
- All public bookings require a valid tenant (subdomain or header).
- Guest bookings start in
PENDINGstatus until email verification. - If guest email matches existing tenant customer, customer context is used.
- Past dates/times are rejected by server validation.
- See
booki-api/src/validations/booking.validation.tsfor exact fields.
