Booking Engine Support API
Status: Shipped 2026-04 in
hmp-property-service(branchfeat/booking-engine-serviceclient). All endpoints are JWT-gated and accept either user or service-account tokens.Note:
api-specs/openapi.yamlshould be regenerated from this Markdown spec in a follow-up task. Until then, this file is the source of truth.
This API exposes 8 read-only endpoints on hmp-property-service that
hmp-reservation-service calls when serving guest-facing booking flows. They were
introduced as part of the xPrisma → ServiceClient migration: previously the reservation
service reached directly into the property service's database, which crashed in production
because reservation-service's Prisma client doesn't have the roomType, room, ratePlan,
or property models.
All endpoints live under the /properties/:propertyId/booking-engine/* prefix and require:
- A valid JWT in
Authorization: Bearer <token>(user or service-account) - A matching
x-tenant-idheader - Receiver-side
assertPropertyTenant()enforcement (the property is verified to belong to the claimed tenant — see Multi-Property Isolation)
1. GET /properties/:propertyId/booking-engine/availability-snapshot
Returns a per-day occupancy snapshot for the date range, used by the booking engine to render the availability calendar.
Auth
Authorization: Bearer <jwt> (user or service-account), x-tenant-id
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
propertyId | UUID | Yes | The property to query |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
from | ISO date | Yes | Start date (inclusive) |
to | ISO date | Yes | End date (inclusive). Max range: 365 days |
Response 200
{
"data": {
"propertyId": "40b29005-5196-4ba7-a71d-94323caee4dc",
"from": "2026-04-10",
"to": "2026-04-20",
"snapshot": [
{
"date": "2026-04-10",
"totalRooms": 22,
"availableRooms": 12,
"byRoomType": [
{ "roomTypeId": "...", "code": "DLX", "available": 5, "total": 8 },
{ "roomTypeId": "...", "code": "STD", "available": 7, "total": 14 }
]
}
]
}
}
Status Codes
200— snapshot returned400— invalid date format ortobeforefrom403— property does not belong to claimed tenant404— property not found
2. GET /properties/:propertyId/booking-engine/room-types
Returns all active room types for the property.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
includeInactive | boolean | No | Default false. Set true for admin views |
Response 200
{
"data": [
{
"id": "uuid",
"code": "DLX",
"name": "Deluxe King",
"description": "King bed, garden view",
"baseOccupancy": 2,
"maxOccupancy": 3,
"totalRooms": 8,
"amenities": ["wifi", "ac", "minibar"],
"imageUrls": ["https://..."],
"isActive": true
}
]
}
Status Codes
200— list returned (may be empty)403— property does not belong to claimed tenant
3. GET /properties/:propertyId/booking-engine/rate-plans
Returns all active rate plans for the property.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
roomTypeId | UUID | No | Filter to rate plans applicable to this room type |
channel | string | No | Filter by sales channel (direct, booking-com, etc.) |
Response 200
{
"data": [
{
"id": "uuid",
"code": "BAR",
"name": "Best Available Rate",
"description": "Flexible, refundable",
"currency": "INR",
"isRefundable": true,
"cancellationPolicyId": "uuid",
"applicableRoomTypeIds": ["uuid", "uuid"]
}
]
}
4. GET /properties/:propertyId/booking-engine/rate-plans/:ratePlanId/with-rates
Returns a single rate plan with daily rates expanded for the date range.
Path Parameters
| Name | Type | Required |
|---|---|---|
propertyId | UUID | Yes |
ratePlanId | UUID | Yes |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
roomTypeId | UUID | Yes | The room type to price |
from | ISO date | Yes | Start date |
to | ISO date | Yes | End date (max 90 days) |
Response 200
{
"data": {
"ratePlan": { "id": "uuid", "code": "BAR", "name": "..." },
"roomTypeId": "uuid",
"rates": [
{ "date": "2026-04-10", "amount": 4500, "currency": "INR" },
{ "date": "2026-04-11", "amount": 4500, "currency": "INR" }
]
}
}
5. GET /properties/:propertyId/booking-engine/room-status-counts
Returns room counts grouped by status. Used by the dashboard housekeeping widget and the booking engine "X rooms left at this rate" indicator.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
statuses | string[] | Yes | Comma-separated. Valid: clean, dirty, inspected, out-of-order, out-of-service |
roomTypeIds | UUID[] | No | Comma-separated. Limits to these room types |
Response 200
{
"data": {
"counts": {
"clean": 14,
"dirty": 5,
"inspected": 2,
"out-of-order": 1,
"out-of-service": 0
}
}
}
6. GET /properties/:propertyId/booking-engine/room-count
Returns the total count of rooms (active + inactive) for the property. Used for capacity ceilings.
Response 200
{
"data": {
"propertyId": "uuid",
"totalRooms": 22,
"activeRooms": 22
}
}
7. GET /properties/:propertyId/booking-engine/available-room
Returns a single specific available room of the requested type, used during reservation finalization to bind a booking to a physical room.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
roomTypeId | UUID | Yes | The required room type |
excludeIds | UUID[] | No | Comma-separated room IDs to skip (already held in another cart) |
Response 200
{
"data": {
"roomId": "uuid",
"roomNumber": "204",
"roomTypeId": "uuid",
"status": "clean"
}
}
Response 404
{
"error": { "code": "NO_ROOM_AVAILABLE", "message": "No available room of type DLX" }
}
8. GET /properties/:propertyId/booking-engine/room-types-with-rate-plans
Convenience endpoint that returns all room types with their applicable rate plans nested. Used by the booking engine's initial load to avoid N+1 round trips.
Response 200
{
"data": [
{
"roomType": {
"id": "uuid",
"code": "DLX",
"name": "Deluxe King",
"totalRooms": 8
},
"ratePlans": [
{ "id": "uuid", "code": "BAR", "name": "Best Available Rate", "currency": "INR" },
{ "id": "uuid", "code": "AP", "name": "Advance Purchase", "currency": "INR" }
]
}
]
}
ServiceClient Method Mapping
The TypeScript counterpart in @hmp/service-clients/PropertyServiceClient exposes one
method per endpoint:
class PropertyServiceClient extends BaseServiceClient {
getAvailabilitySnapshot(args: { propertyId: string; from: string; to: string }): Promise<Snapshot>;
getRoomTypes(args: { propertyId: string; includeInactive?: boolean }): Promise<RoomType[]>;
getRatePlans(args: { propertyId: string; roomTypeId?: string; channel?: string }): Promise<RatePlan[]>;
getRatePlanWithRates(args: { propertyId: string; ratePlanId: string; roomTypeId: string; from: string; to: string }): Promise<RatePlanWithRates>;
getRoomStatusCounts(args: { propertyId: string; statuses: string[]; roomTypeIds?: string[] }): Promise<StatusCounts>;
getRoomCount(args: { propertyId: string }): Promise<RoomCount>;
getAvailableRoom(args: { propertyId: string; roomTypeId: string; excludeIds?: string[] }): Promise<Room | null>;
getRoomTypesWithRatePlans(args: { propertyId: string }): Promise<RoomTypeWithRatePlans[]>;
}
All methods read tenant scope from AsyncLocalStorage and forward Authorization,
x-tenant-id, and x-property-id automatically — no caller boilerplate.