This guide summarizes what frontend consumers need to change after recent pricing/import updates.
PUT /v1/itinerary/:id/pricing-selection should no longer be called.PUT /v1/itinerary/:id/pricing-table.POST /v1/bookings with required pricingSelection block.Applies to:
Use separated admin/customer write paths:
PUT /v1/itinerary/:id/pricing-table (JWT + TRIP permission)POST /v1/bookings with required pricingSelection (booking-jwt)Booking create integration:
POST /v1/bookings now requires pricingSelection payload (b2c) and computes booking.priceDetails from it.pricingSelection is missing, booking create fails validation.Both return pricing response in the same shape as GET /v1/itinerary/:id/price-details.
Usage split:
PUT /pricing-table: update base pricing rows/slabs (rate card)POST /bookings pricingSelection: update customer selection only (category/slab/travelers/channel)activities_only, customer selection request must send only traveler counts; activity prices are admin-managed via /pricing-table.Category fields are numeric enum values (not string labels) — HotelCategoryEnum (src/shared/enums/hotel-category.enum.ts):
1 = LUXURY2 = PREMIUM3 = SUPER_DELUXE4 = DELUXE5 = STANDARDFor hotel_transfer and hotel_only scenarios, availableCategories in the price-details response always returns all five values [1,2,3,4,5]. Use categoryPerPersonPrices to determine which categories have actual pricing rows.
Vehicle slab auto-selection uses adults + children (extraMattressCount/childrenWithBed and infants are excluded from slab banding):
capacityMin ≤ totalPax ≤ capacityMax<= 3 pax → selects 1-3 slab; > 3 pax → selects 4-N slabchannel=b2b or omitted), the pricing summary category is derived from itinerary tripType via normalizeTripTypeToCategory().selectedCategory query param is ignored for b2b summary computation.markupPercent, markupType, markupValue, and markupAmount as separate fields.baseCost is the raw cost before markup is applied.defaultHotelCategory removeddefaultHotelCategory.tripType as canonical category source.Markup entity per itinerary (never stored in pricing rows):isGlobal=true) → fallbackMarkup record with category='gst', same priority order. When GST is not configured, gstPercent and gstAmount in the summary are null, not 0.FLAT (type=1) and PERCENTAGE (type=2).POST /v1/bookings validates that pricingSelection.channel is "b2c" or omitted.channel: "b2b" in a booking request returns ERR_BOOKING_PRICING_CHANNEL_MUST_BE_B2C."b2c" inside the booking service regardless.Endpoint: POST /v1/itinerary/import-itinerary-full
Use IDs only:
hotelIds: number[]transferIds: number[]Do not send old hotels[] / transfers[] create-resolve payloads.
activities[] are created and mapped day-wise.
Required fields per item:
dayOrder (number)title (string)startTime (string, mandatory)Optional fields:
duration (minutes)description, catgType, subCatgType, activityType, latitude, longitudeExplicit mapping supported via:
hotelDayMappings: [{ hotelId, dayOrder }]Validation:
Backward compatibility:
hotelDayMappings is omitted, backend maps hotel to all itinerary days with matching location.For check-inventory, search-inventory, and inventory responses, hotel objects now include:
id, name, cityId, categoryaddress, description, starRating, checkInTime, checkOutTime, roomTypePOST /v1/hotel/check-inventory also returns:
hotels: [] (all matched priority-name hotels)hotel (first match for backward compatibility)For check-by-name, search, and inventory responses, transfer objects now include:
id, vehicleName, vehicleType, seaterdescription, notes, isActive, createdAt, updatedAtPOST /v1/transfer/check-by-name request is bulk:
{ "items": [{ "name": "Wagon R / Similar" }, { "name": "Innova / Similar" }] }
Response includes requestedName per item.
Transfer update validation:
The following endpoints are not route-changed by these updates:
GET /v1/itinerary/agentGET /v1/itineraryGET /v1/itinerary/:idGET /v1/hotel/itinerary/:idGET /v1/transfer/itinerary/:id/transfersGET /v1/days/itinerary-details/:idPotential response/data adjustments frontend should tolerate:
defaultHotelCategory field removaltripType category interpretationBase: /v1/markup
POST /v1/markup create markupPUT /v1/markup/:id update markupDELETE /v1/markup/:id soft delete markupGET /v1/markup list markupsGET /v1/markup/:id get markup detailsGET /v1/markup/unmapped-itineraries list trips not mapped in any specific markupBehavior rules:
appliesTo structure:ALL_TRIPSSPECIFIC_TRIPS with tripIds and trips[]POST /v1/markup
{
"name": "Summer promo - selected trips",
"isGlobal": false,
"itineraryIds": [948, 950, 958],
"type": 2,
"amount": 7.5,
"notes": "Percentage markup for selected trips",
"isActive": true
}
POST /v1/markup
{
"name": "Default all-trips fallback",
"isGlobal": true,
"type": 1,
"amount": 1200,
"notes": "Flat fallback markup for all trips",
"isActive": true
}
PUT /v1/markup/:id
{
"name": "Summer promo - revised",
"isActive": true,
"type": 2,
"amount": 8.25,
"notes": "Revised percentage",
"addItineraryIds": [970],
"removeItineraryIds": [950]
}
appliesTo){
"id": 12,
"name": "Summer promo - revised",
"isGlobal": false,
"type": 2,
"amount": "8.25",
"isActive": true,
"appliesTo": {
"type": "SPECIFIC_TRIPS",
"tripIds": [948, 958, 970],
"trips": [
{ "id": 948, "name": "Sikkim Delight" },
{ "id": 958, "name": "North East Premium" },
{ "id": 970, "name": "Bhutan Escape" }
]
}
}
For all-trips markup:
{
"appliesTo": {
"type": "ALL_TRIPS",
"tripIds": [],
"trips": []
}
}
checkInTime is optional in hotel create/update formscheckInTime as mandatory.checkInTime can be omitted.Primary uniqueness is now enforced within:
itineraryId + locationId + categoryFrontend requirements:
locationId and category in POST /v1/hotel/map-itinerary.locationId and category in PATCH /v1/hotel/primary.Example:
{
"hotelId": 647,
"itineraryId": 948,
"locationId": 12,
"category": 1,
"isPrimary": true
}
Error handling:
PATCH /v1/hotel/primary is ambiguous without scope, backend can return:ERR_HOTEL_PRIMARY_SCOPE_REQUIREDWhen updating hotel category (categoryId) in hotel update API:
Error returned:
ERR_HOTEL_CATEGORY_CHANGE_REQUIRES_UNPUBLISHhotel_itinerary_mapping.category for that hotel’s mappings.Endpoint:
POST /v1/multi-tenancy/tenant-media-urlsAuth/headers:
Authorization: Bearer <token>)tenantId)Request body:
{
"tenantId": 1
}
Response highlights:
message = SUC_TENANT_MEDIA_URLS_FETCHEDdata.mediaByBucketFolder grouped by S3BucketTypeEnum folder keysdata.summary.totalFoldersdata.summary.totalMediaItemsdata.sourceErrors[] for best-effort source-level failuresOption C URL resolution rule:
<awsS3BaseUrl>/<folder>/<fileName>fallbackLinkUsed to indicate fallback behavior.Negative cases:
tenantId <= 0) -> validation errorERR_TENANT_NOT_FOUND&&&tenantIdPOST /v1/bookings under pricingSelectionPUT /v1/itinerary/:id/pricing-tablehotelIds, transferIds, activities, optional hotelDayMappingsstartTime for each imported activitydefaultHotelCategorycheckInTime as optional in create/update formslocationId + category when calling PATCH /v1/hotel/primaryERR_HOTEL_CATEGORY_CHANGE_REQUIRES_UNPUBLISH on hotel category updatesPOST /v1/multi-tenancy/tenant-media-urls and consume grouped mediaByBucketFolder