Skip to main content

Booking Engine Support API

Status: Shipped 2026-04 in hmp-property-service (branch feat/booking-engine-serviceclient). All endpoints are JWT-gated and accept either user or service-account tokens.

Note: api-specs/openapi.yaml should 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-id header
  • 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​

NameTypeRequiredDescription
propertyIdUUIDYesThe property to query

Query Parameters​

NameTypeRequiredDescription
fromISO dateYesStart date (inclusive)
toISO dateYesEnd 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 returned
  • 400 — invalid date format or to before from
  • 403 — property does not belong to claimed tenant
  • 404 — property not found

2. GET /properties/:propertyId/booking-engine/room-types​

Returns all active room types for the property.

Query Parameters​

NameTypeRequiredDescription
includeInactivebooleanNoDefault 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​

NameTypeRequiredDescription
roomTypeIdUUIDNoFilter to rate plans applicable to this room type
channelstringNoFilter 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​

NameTypeRequired
propertyIdUUIDYes
ratePlanIdUUIDYes

Query Parameters​

NameTypeRequiredDescription
roomTypeIdUUIDYesThe room type to price
fromISO dateYesStart date
toISO dateYesEnd 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​

NameTypeRequiredDescription
statusesstring[]YesComma-separated. Valid: clean, dirty, inspected, out-of-order, out-of-service
roomTypeIdsUUID[]NoComma-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​

NameTypeRequiredDescription
roomTypeIdUUIDYesThe required room type
excludeIdsUUID[]NoComma-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.