Backend: HolidayStreet Trip Backend
Module: Hotel (/hotel)
Last Updated: 2026-05-05
Phase: 02-hotel-property-enhancement (all 6 plans complete)
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.0–5.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. |
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:
sizeSqftmust be a whole number (integer)maxOccupancycannot exceed 4bedTypeis sent as a string name. Backend searches existing BedType master table; if found, maps to its ID. If not found, creates a new BedType record.
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) |
POST /hotelContent-Type: multipart/form-data
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP
Called when the user clicks "Next" on the "Add New Property" form for the first time.
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);
});
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"
}
}
| 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 |
PUT /hotel/{id}Content-Type: multipart/form-data
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP
Called when the user edits an existing property and clicks "Next" (or "Save").
Same as Create, with these differences:
rooms: include id for existing rooms you want to update; omit id for new rooms to add.specialFeatures: same — include id to update; omit id to add new.toDeleteRoomIds — array of room IDs to removetoDeleteSpecialFeatureIds — array of special feature IDs to removetoDeleteHotelMediaIds — array of media IDs to removeconst 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);
});
200 OKSame shape as Create Hotel response (SUC_HOTEL_UPDATED).
{
"message": "SUC_HOTEL_UPDATED",
"data": { /* same full hotel object as create response */ }
}
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 |
GET /hotel/{hotel_id}Content-Type: application/json
Auth: Bearer token + JWT cookie
Permission: Permission.TRIP
Called when the user opens an existing property for editing (pre-populate the form), or when viewing property details.
200 OKReturns 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"
}
}
}
| Status | Message | When |
|---|---|---|
404 |
ERR_HOTEL_NOT_FOUND |
Hotel ID does not exist |
GET /hotelContent-Type: application/json
Auth: Bearer token + JWT cookie
Permission: Permission.HOTEL_INVENTORY
| 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 |
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
roomsarray (id,name,sizeSqft,maxOccupancy,occupancy,bedTypeId,roomView). UseGET /hotel/{id}for full room details including resolvedbedTypename.
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.
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
bedTypefield of the room object. The backend will resolve it to the correct ID (or create a new BedType if it doesn't exist).
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.
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.
| Value | Label |
|---|---|
1 |
Hotel |
2 |
Villa |
3 |
Apartment |
| Value | Label |
|---|---|
1 |
Luxury |
2 |
Premium |
3 |
Super Deluxe |
4 |
Deluxe |
5 |
Standard |
| Value | Label |
|---|---|
1 |
Single Room |
2 |
Double Room |
3 |
King/Queen Bed |
4 |
Suite Room |
5 |
Private Villa |
| Value | Label |
|---|---|
1 |
USD |
2 |
EUR |
3 |
GBP |
4 |
INR |
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"]));
const formData = new FormData();
// ... other fields ...
imageFiles.forEach((file) => {
formData.append("mediaFile", file);
});
await fetch("/hotel", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: formData,
});
GET /hotel/{id}data.rooms → form rooms field (include id for each existing room)data.specialFeatures → form specialFeatures field (include id)data.holidayStyles → extract just the id values: data.holidayStyles.map(h => h.id)data.media.multiMedia → display gallery; store id for each image for deletionWhen the user removes a room, special feature, or gallery image in the UI:
id to toDeleteRoomIds array. Do NOT include it in the rooms array.id to toDeleteSpecialFeatureIds. Do NOT include it in specialFeatures.id to toDeleteHotelMediaIds. Do NOT re-upload the file.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.
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