Frontend API Integration Guide — Hotel Property Management

Backend: HolidayStreet Trip Backend
Module: Hotel (/hotel)
Last Updated: 2026-05-05
Phase: 02-hotel-property-enhancement (all 6 plans complete)


Table of Contents

  1. Wireframe-to-API Field Map
  2. Create Hotel (Add New Property)
  3. Update Hotel
  4. Get Hotel by ID
  5. List Hotels
  6. Search Endpoints
  7. Enums & Reference Data
  8. Common Patterns

1. Wireframe-to-API Field Map

This table maps every field shown in the "Add New Property" wireframe to the corresponding API field name, data type, and validation rules.

Wireframe Field API Field Type Required Notes
Search Hotel cityId number No ID from the cities master list. Use dropdown with search.
Type (Hotel/Villa/Apartment) propertyType number enum No 1=HOTEL, 2=VILLA, 3=APARTMENT
Name name string Yes Max 100 chars, trimmed
Address address string Yes Max 255 chars
Region region string No Max 255 chars, e.g. "Downtown"
Star Rating starRating number No 1.05.0, max 1 decimal place
Hotel Category categoryId number enum Yes 1=LUXURY, 2=PREMIUM, 3=SUPER_DELUXE, 4=DELUXE, 5=STANDARD
Check-in Time checkInTime string No Format: "14:00:00" (24h)
Check-out Time checkOutTime string No Format: "11:00:00" (24h)
Description description string No Max 5000 chars (rich text / HTML)
Amenities amenities number[] No Array of amenity IDs. JSON-string or comma-separated accepted.
Holiday Style holidayStyles number[] No Array of trip_catg_types IDs. JSON-string or comma-separated accepted. Filter: isActive=true AND isDeleted=false.
Rooms rooms object[] Yes on create Min 1 room required on create. Max 30 rooms. Each room is an object (see below).
Special About This Property specialFeatures object[] No Max 3 title/description pairs (see below).
Gallery mediaFile File[] No Multipart upload. Max 10 images. Send as multipart/form-data.
Tags tags string[] No Array of tag names. JSON-string or comma-separated accepted.

Room Object Schema (rooms[])

Each element in the rooms array:

{
  "name": "Deluxe King Room",
  "description": "Spacious room with city view...",
  "sizeSqft": 450,
  "maxOccupancy": 2,
  "occupancy": "2 Adults, 1 Child",
  "bedType": "King Size",
  "roomView": "Ocean View"
}
Field Type Required Max Length
name string No 255
description string No 5000 (rich text)
sizeSqft number No Must be integer (whole number)
maxOccupancy number No Max 4
occupancy string No 50
bedType string No 50 — Bed type name. Backend searches existing BedType master; if found, maps to its ID. If not found, creates a new BedType record.
roomView string No 100

Wireframe Note: "If any info is not displayed then that field should not populate on b2c page."
API Note: All room fields are optional individually. Only the array minimum length (@ArrayMinSize(1)) is enforced on create. Validation Notes:

Special Feature Object Schema (specialFeatures[])

Each element in the specialFeatures array:

{
  "title": "Rooftop Infinity Pool",
  "description": "Enjoy panoramic views..."
}
Field Type Required Max Length
title string No 255
description string No 5000 (rich text)

2. Create Hotel — POST /hotel

Content-Type: multipart/form-data
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP

When to Use

Called when the user clicks "Next" on the "Add New Property" form for the first time.

FormData Fields

All fields below are sent as FormData key-value pairs. Nested objects (rooms, specialFeatures, holidayStyles, amenities, tags) must be JSON-stringified before appending to FormData.

const formData = new FormData();

// --- Basic Info (Left Panel) ---
formData.append("name", "Grand Hotel Dubai");
formData.append("address", "123 Main St, Dubai");
formData.append("region", "Downtown");
formData.append("propertyType", "1");        // HOTEL=1, VILLA=2, APARTMENT=3
formData.append("starRating", "4.5");
formData.append("categoryId", "1");          // LUXURY=1
formData.append("checkInTime", "14:00:00");
formData.append("checkOutTime", "11:00:00");
formData.append("description", "Luxurious hotel with great services...");

// --- Amenities & Holiday Styles ---
formData.append("amenities", JSON.stringify([1, 2, 3]));
formData.append("holidayStyles", JSON.stringify([5, 8]));   // TripCatgTypes IDs

// --- Tags ---
formData.append("tags", JSON.stringify(["beach", "relax"]));

// --- Rooms (mandatory: at least 1, max 30) ---
formData.append("rooms", JSON.stringify([
  {
    name: "Deluxe King Room",
    description: "Spacious room with city view...",
    sizeSqft: 450,
    maxOccupancy: 2,
    occupancy: "2 Adults, 1 Child",
    bedType: "King Size",
    roomView: "Ocean View"
  },
  {
    name: "Standard Twin Room",
    description: "Cozy and affordable...",
    sizeSqft: 320,
    maxOccupancy: 2,
    occupancy: "2 Adults",
    bedType: "Twin Beds",
    roomView: "Garden View"
  }
]));

// --- Special About This Property (up to 3) ---
formData.append("specialFeatures", JSON.stringify([
  { title: "Rooftop Infinity Pool", description: "Enjoy panoramic views..." },
  { title: "Private Beach Access", description: "Direct access to white sand..." },
  { title: "Fine Dining", description: "Award-winning chefs..." }
]));

// --- Gallery (max 10 files) ---
// Append each file individually with key "mediaFile"
imageFiles.forEach((file) => {
  formData.append("mediaFile", file);
});

Response — 200 OK

{
  "message": "SUC_HOTEL_CREATED",
  "data": {
    "id": 42,
    "name": "Grand Hotel Dubai",
    "address": "123 Main St, Dubai",
    "region": "Downtown",
    "propertyType": 1,
    "starRating": 4.5,
    "categoryId": 1,
    "checkInTime": "14:00:00",
    "checkOutTime": "11:00:00",
    "description": "Luxurious hotel with great services...",
    "cityId": 1,
    "isActive": true,
    "media": {
      "multiMedia": [
        { "id": 101, "filename": "https://cdn.example.com/hotel/abc.jpg" }
      ],
      "attachments": []
    },
    "tags": [
      { "id": 1, "name": "beach" },
      { "id": 2, "name": "relax" }
    ],
    "itineraries": [],
    "amenities": [
      { "id": 1, "name": "Gym", "status": true, "url": "..." }
    ],
    "rooms": [
      {
        "id": 201,
        "name": "Deluxe King Room",
        "description": "Spacious room with city view...",
        "sizeSqft": 450,
        "occupancy": "2 Adults, 1 Child",
        "bedType": "King Size",
        "roomView": "Ocean View"
      }
    ],
    "specialFeatures": [
      {
        "id": 301,
        "title": "Rooftop Infinity Pool",
        "description": "Enjoy panoramic views..."
      }
    ],
    "holidayStyles": [
      { "id": 5, "name": "Adventure Travel" }
    ],
    "createdAt": "2026-05-05T10:00:00.000Z",
    "updatedAt": "2026-05-05T10:00:00.000Z"
  }
}

Error Responses

Status Message When
422 Address is required.&&&address Missing address
422 Hotel name is required.&&&name Missing name
422 At least one room is required.&&&rooms&&&ERROR_MESSAGE rooms array has < 1 element
422 Cannot add more than 30 rooms.&&&rooms&&&ERROR_MESSAGE rooms array has > 30 elements
422 Cannot add more than 3 special features.&&&specialFeatures&&&ERROR_MESSAGE specialFeatures array has > 3 elements
422 Star rating must be between 1 and 5 with at most 1 decimal place.&&&starRating&&&ERROR_MESSAGE starRating has more than 1 decimal place or is outside 1-5
422 Room size must be a whole number.&&&sizeSqft&&&ERROR_MESSAGE sizeSqft is not an integer
422 Max occupancy cannot exceed 4.&&&maxOccupancy&&&ERROR_MESSAGE maxOccupancy > 4
422 Category must be a valid hotel category.&&&category Invalid categoryId
422 Rooms must be a valid JSON array.&&&rooms&&&ERROR_MESSAGE rooms is not valid JSON
404 ERR_CITY_NOT_FOUND cityId does not exist
400 ERR_HOTEL_ALREADY_EXISTS Duplicate name + address + city combination

3. Update Hotel — PUT /hotel/{id}

Content-Type: multipart/form-data
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP

When to Use

Called when the user edits an existing property and clicks "Next" (or "Save").

FormData Fields

Same as Create, with these differences:

const formData = new FormData();

// --- Changed fields only ---
formData.append("name", "Grand Hotel Dubai — Updated");
formData.append("starRating", "5.0");

// --- Update existing room (has id) + add new room (no id) ---
formData.append("rooms", JSON.stringify([
  {
    id: 201,                       // existing room → UPDATE
    name: "Deluxe King Room",
    description: "Updated description...",
    sizeSqft: 500,
    occupancy: "2 Adults, 1 Child",
    bedType: "King Size",
    roomView: "Sea View"
  },
  {                                // new room → INSERT
    name: "Presidential Suite",
    description: "Ultimate luxury...",
    sizeSqft: 1200,
    occupancy: "4 Adults",
    bedType: "California King",
    roomView": "Panoramic Ocean View"
  }
]));

// --- Delete a room ---
formData.append("toDeleteRoomIds", JSON.stringify([202]));

// --- Delete a special feature ---
formData.append("toDeleteSpecialFeatureIds", JSON.stringify([302]));

// --- Delete an old gallery image ---
formData.append("toDeleteHotelMediaIds", JSON.stringify([101]));

// --- Add new gallery images ---
newImageFiles.forEach((file) => {
  formData.append("mediaFile", file);
});

Response — 200 OK

Same shape as Create Hotel response (SUC_HOTEL_UPDATED).

{
  "message": "SUC_HOTEL_UPDATED",
  "data": { /* same full hotel object as create response */ }
}

Error Responses

Same as Create, plus:

Status Message When
404 ERR_HOTEL_NOT_FOUND Hotel ID does not exist
400 ERR_HOTEL_ADDRESS_CITY_STATE_MISMATCH updatedHotelAddress city/state does not match stored location

4. Get Hotel by ID — GET /hotel/{hotel_id}

Content-Type: application/json
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP

When to Use

Called when the user opens an existing property for editing (pre-populate the form), or when viewing property details.

Response — 200 OK

Returns the full hotel object including all nested arrays:

{
  "message": "SUC_HOTEL_FETCHED",
  "data": {
    "id": 42,
    "name": "Grand Hotel Dubai",
    "address": "123 Main St, Dubai",
    "region": "Downtown",
    "propertyType": 1,
    "starRating": 4.5,
    "categoryId": 1,
    "checkInTime": "14:00:00",
    "checkOutTime": "11:00:00",
    "checkInDate": "2025-05-24",
    "checkOutDate": "2025-05-30",
    "description": "Luxurious hotel with great services...",
    "roomType": 4,
    "roomPreference": "Non-smoking room with king-size bed...",
    "notes": "Special requests...",
    "price": 2000.00,
    "currencyType": 1,
    "cityId": 1,
    "isActive": true,
    "media": {
      "multiMedia": [
        { "id": 101, "filename": "https://cdn.example.com/hotel/abc.jpg" }
      ],
      "attachments": []
    },
    "tags": [
      { "id": 1, "name": "beach" },
      { "id": 2, "name": "relax" }
    ],
    "itineraries": [
      { "id": 10, "name": "Dubai Explorer", "isPrimary": true }
    ],
    "amenities": [
      { "id": 1, "name": "Gym", "status": true, "url": "..." }
    ],
    "rooms": [
      {
        "id": 201,
        "name": "Deluxe King Room",
        "description": "Spacious room...",
        "sizeSqft": 450,
        "maxOccupancy": 2,
        "occupancy": "2 Adults, 1 Child",
        "bedTypeId": 1,
        "bedType": "King Size",
        "roomView": "Ocean View"
      }
    ],
    "specialFeatures": [
      {
        "id": 301,
        "title": "Rooftop Infinity Pool",
        "description": "Enjoy panoramic views..."
      }
    ],
    "holidayStyles": [
      { "id": 5, "name": "Adventure Travel" }
    ],
    "city": {
      "id": 1,
      "name": "Dubai"
    }
  }
}

Error Responses

Status Message When
404 ERR_HOTEL_NOT_FOUND Hotel ID does not exist

5. List Hotels — GET /hotel

Content-Type: application/json
Auth: Bearer token + JWT cookie
Permission: Permission.HOTEL_INVENTORY

Query Parameters

Param Type Default Description
offset number 0 Pagination offset
limit number 20 Page size
search string Filter by hotel name (ILIKE)
isActive boolean Filter by active status
type number Filter by categoryId

Response — 200 OK

{
  "message": "SUC_HOTELS_FETCHED",
  "data": {
    "hotels": [
      {
        "id": 42,
        "name": "Grand Hotel Dubai",
        "starRating": 4.5,
        "checkInTime": "14:00:00",
        "checkInDate": "2025-05-24",
        "checkOutDate": "2025-05-30",
        "checkOutTime": "11:00:00",
        "description": "Luxurious hotel...",
        "roomType": 4,
        "roomPreference": "Non-smoking...",
        "notes": "Special requests...",
        "price": 2000.00,
        "currencyType": 1,
        "address": "123 Main St, Dubai",
        "isActive": true,
        "createdAt": "2026-05-01T10:00:00.000Z",
        "updatedAt": "2026-05-05T10:00:00.000Z",
        "categoryId": 1,
        "city": {
          "id": 1,
          "name": "Dubai",
          "state": { "id": 1, "name": "Dubai" },
          "country": { "id": 1, "name": "United Arab Emirates" }
        },
        "media": {
          "multiMedia": [ /* ... */ ],
          "attachments": []
        },
        "tags": [ /* ... */ ],
        "rooms": [
          { "id": 201, "name": "Deluxe King Room" }
        ]
      }
    ],
    "total": 150
  }
}

Note: The list response includes a simplified rooms array (id, name, sizeSqft, maxOccupancy, occupancy, bedTypeId, roomView). Use GET /hotel/{id} for full room details including resolved bedType name.


6. Search Endpoints

6.1 Search Bed Types — GET /hotel/bed-types?search={query}

Auth: Bearer token + JWT cookie
Permission: Permission.TRIP

Returns a list of active bed types matching the search query (case-insensitive). Limited to 50 results.

Response — 200 OK

{
  "message": "SUC_BED_TYPES_FETCHED",
  "data": [
    { "id": 1, "name": "King Size" },
    { "id": 2, "name": "Queen Size" },
    { "id": 3, "name": "Twin Beds" }
  ]
}

Usage: Use this endpoint to power an autocomplete dropdown for the "Bed Type" field in the room form. When the user selects or types a bed type name, send the name string in the bedType field of the room object. The backend will resolve it to the correct ID (or create a new BedType if it doesn't exist).


6.2 Search Tags — GET /hotel/tags?search={query}

Auth: Bearer token + JWT cookie
Permission: Permission.TRIP

Returns a list of existing tags for the current tenant matching the search query (case-insensitive). Limited to 50 results.

Response — 200 OK

{
  "message": "SUC_TAGS_FETCHED",
  "data": [
    { "id": 1, "name": "beach" },
    { "id": 2, "name": "relax" },
    { "id": 3, "name": "family-friendly" }
  ]
}

Usage: Use this endpoint to power an autocomplete or tag picker for the hotel tags field.


7. Enums & Reference Data

PropertyTypeEnum

Value Label
1 Hotel
2 Villa
3 Apartment

HotelCategoryEnum

Value Label
1 Luxury
2 Premium
3 Super Deluxe
4 Deluxe
5 Standard

RoomTypesEnum

Value Label
1 Single Room
2 Double Room
3 King/Queen Bed
4 Suite Room
5 Private Villa

CurrencyTypeEnum

Value Label
1 USD
2 EUR
3 GBP
4 INR

8. Common Patterns

8.1 JSON-Stringify Nested Arrays for FormData

All nested objects/arrays (rooms, specialFeatures, holidayStyles, amenities, tags) must be JSON.stringify()-ed before appending to FormData. The backend parses them automatically.

formData.append("rooms", JSON.stringify(roomsArray));
formData.append("specialFeatures", JSON.stringify(featuresArray));
formData.append("holidayStyles", JSON.stringify([5, 8]));
formData.append("amenities", JSON.stringify([1, 2, 3]));
formData.append("tags", JSON.stringify(["beach", "relax"]));

8.2 Gallery Upload (Multipart)

const formData = new FormData();
// ... other fields ...
imageFiles.forEach((file) => {
  formData.append("mediaFile", file);
});

await fetch("/hotel", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
  body: formData,
});

8.3 Pre-Populate Edit Form

  1. Call GET /hotel/{id}
  2. Map data.rooms → form rooms field (include id for each existing room)
  3. Map data.specialFeatures → form specialFeatures field (include id)
  4. Map data.holidayStyles → extract just the id values: data.holidayStyles.map(h => h.id)
  5. Map data.media.multiMedia → display gallery; store id for each image for deletion

8.4 Handling Deletions on Update

When the user removes a room, special feature, or gallery image in the UI:

  1. Rooms: Add the room's id to toDeleteRoomIds array. Do NOT include it in the rooms array.
  2. Special Features: Add the feature's id to toDeleteSpecialFeatureIds. Do NOT include it in specialFeatures.
  3. Gallery Images: Add the media id to toDeleteHotelMediaIds. Do NOT re-upload the file.

8.5 Holiday Styles (TripCatgTypes)

Holiday styles are sourced from the existing trip_catg_types master table. The backend validates that provided IDs exist and are active (isActive=true, isDeleted=false).

To populate the dropdown: You may need a separate endpoint to list TripCatgTypes. If one does not exist, ask the backend team to expose GET /trip-categories or similar.


Appendix: Full Request Example (Create)

curl -X POST "https://api.holidaystreet.com/hotel" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: multipart/form-data" \
  -F "name=Grand Hotel Dubai" \
  -F "address=123 Main St, Dubai" \
  -F "region=Downtown" \
  -F "propertyType=1" \
  -F "starRating=4.5" \
  -F "categoryId=1" \
  -F "checkInTime=14:00:00" \
  -F "checkOutTime=11:00:00" \
  -F "description=Luxurious hotel with great services" \
  -F "amenities=[1,2,3]" \
  -F "holidayStyles=[5,8]" \
  -F 'rooms=[{"name":"Deluxe King Room","description":"Spacious...","sizeSqft":450,"maxOccupancy":2,"occupancy":"2 Adults, 1 Child","bedType":"King Size","roomView":"Ocean View"}]' \
  -F 'specialFeatures=[{"title":"Rooftop Infinity Pool","description":"Enjoy panoramic views..."}]' \
  -F "tags=[\"beach\",\"relax\"]" \
  -F "mediaFile=@hotel-photo-1.jpg" \
  -F "mediaFile=@hotel-photo-2.jpg"

End of Guide