Attributes by Category
The attributes JSONB on value_added_service is a Jackson-polymorphic record.
The category field on the parent row is the discriminator that selects the
concrete type. Every shape below shows the JSON payload and the canonical
example from production lingo.
The full list of categories:
| Category | Java type | Use for |
|---|---|---|
FOOD | FoodAttributes | BBQ, snacks, breakfast, high tea, dinner combos |
EXPERIENCE | ExperienceAttributes | Bonfire, movie night, rain dance, DJ night |
TRANSPORT | TransportAttributes | Sedan, SUV, tempo traveller with hours + km |
CHEF | ChefAttributes | Personal chef, master chef; with grocery on actuals |
WELLNESS | WellnessAttributes | Spa, massage, yoga sessions |
OTHER | OtherAttributes | Free-form escape hatch — avoid where possible |
FOOD — FoodAttributes
Use for any meal or food add-on. Distinguishes meal slot, veg/non-veg counts, and
explicit includes/excludes (so "Only Snacks (no BBQ)" is queryable, not buried in
the name).
Schema
{
"category": "FOOD",
"mealType": "BREAKFAST | LUNCH | DINNER | HIGH_TEA | SNACKS",
"vegCount": 2,
"nonVegCount": 2,
"snackCount": 4,
"includes": ["BBQ", "Salad", "Dessert"],
"excludes": ["BBQ"],
"cuisines": ["Indian", "Continental"]
}
Example: "BBQ (2 Veg & 2 Non-Veg)"
{
"id": "BBQ_2V_2NV",
"name": "BBQ (2 Veg & 2 Non-Veg)",
"category": "FOOD",
"kind": "SINGLE",
"attributes": {
"category": "FOOD",
"mealType": "DINNER",
"vegCount": 2,
"nonVegCount": 2,
"includes": ["BBQ", "Marinade", "Sides"],
"cuisines": ["Indian"]
}
}
Example: "Only Snacks (no BBQ)"
{
"id": "SNACKS_NO_BBQ",
"name": "Only Snacks (no BBQ)",
"category": "FOOD",
"kind": "SINGLE",
"attributes": {
"category": "FOOD",
"mealType": "SNACKS",
"snackCount": 4,
"includes": ["Pakoras", "Sandwiches", "Chips"],
"excludes": ["BBQ"]
}
}
Example: "Breakfast & High Tea"
{
"id": "BREAKFAST_HIGH_TEA",
"name": "Breakfast & High Tea",
"category": "FOOD",
"kind": "BUNDLE",
"bundlePricingMode": "ROLLUP",
"attributes": {
"category": "FOOD",
"includes": ["Breakfast Buffet", "Evening High Tea"]
}
}
For a true breakfast-plus-high-tea bundle, you'd model it as kind=BUNDLE with two
child VAS rows (one per meal) wired through vas_bundle_item. See
Variants, bundles & choices.
EXPERIENCE — ExperienceAttributes
Use for non-food activities. Captures the activity type, duration, indoor/outdoor, and setup location on the property.
Schema
{
"category": "EXPERIENCE",
"experienceType": "BONFIRE | MOVIE_NIGHT | RAIN_DANCE | POOL_PARTY | DJ_NIGHT | OTHER",
"durationMinutes": 120,
"isOutdoor": true,
"location": "POOLSIDE | LAWN | ROOFTOP | INDOOR | GARDEN",
"setupNotes": "Includes projector, screen and seating for 10"
}
Example: "Bonfire"
{
"id": "BONFIRE",
"name": "Bonfire",
"category": "EXPERIENCE",
"kind": "SINGLE",
"attributes": {
"category": "EXPERIENCE",
"experienceType": "BONFIRE",
"durationMinutes": 120,
"isOutdoor": true,
"location": "LAWN",
"setupNotes": "Firewood, marshmallows, blankets"
}
}
Example: "Movie Night"
{
"id": "MOVIE_NIGHT",
"name": "Movie Night",
"category": "EXPERIENCE",
"kind": "SINGLE",
"attributes": {
"category": "EXPERIENCE",
"experienceType": "MOVIE_NIGHT",
"durationMinutes": 150,
"isOutdoor": false,
"location": "INDOOR",
"setupNotes": "Projector, screen, popcorn, seating for 8"
}
}
Example: "Rain Dance"
{
"id": "RAIN_DANCE",
"name": "Rain Dance",
"category": "EXPERIENCE",
"kind": "SINGLE",
"attributes": {
"category": "EXPERIENCE",
"experienceType": "OTHER",
"durationMinutes": 90,
"isOutdoor": true,
"location": "POOLSIDE"
},
"constraints": {
"minPax": 6,
"leadTimeHours": 48,
"seasons": ["SUMMER", "MONSOON"]
}
}
TRANSPORT — TransportAttributes
Use for vehicle rentals and transfers. Captures vehicle class/model, base envelope
(hours + km), passenger capacity, and whether driver/fuel are included. Overage
rates belong in pricing_config (BasePlusOveragePricing), not here.
Schema
{
"category": "TRANSPORT",
"vehicleClass": "HATCHBACK | SEDAN | SUV | TEMPO_TRAVELLER | LUXURY",
"vehicleModel": "SWIFT_DZIRE",
"vehicleLabel": "Premium Sedan - Swift Dzire",
"baseHours": 4,
"baseKm": 40,
"maxPassengers": 4,
"driverIncluded": true,
"fuelIncluded": true
}
Example: "Premium Sedan – Swift Dzire, 4 Hours / 40 KMs"
Modelled as a VARIANT_PARENT so the same vehicle can be offered with multiple
hour/km envelopes. The variant carries the same shape with its own numbers.
{
"id": "PREMIUM_SEDAN",
"name": "Premium Sedan",
"category": "TRANSPORT",
"kind": "VARIANT_PARENT",
"attributes": {
"category": "TRANSPORT",
"vehicleClass": "SEDAN",
"vehicleModel": "SWIFT_DZIRE",
"vehicleLabel": "Premium Sedan - Swift Dzire",
"maxPassengers": 4,
"driverIncluded": true,
"fuelIncluded": true
}
}
Variant SWIFT_DZIRE_4H_40KM (see variants doc):
{
"id": "SWIFT_DZIRE_4H_40KM",
"vasId": "PREMIUM_SEDAN",
"name": "Swift Dzire - 4 Hours / 40 KMs",
"attributes": {
"category": "TRANSPORT",
"vehicleClass": "SEDAN",
"vehicleModel": "SWIFT_DZIRE",
"baseHours": 4,
"baseKm": 40
}
}
CHEF — ChefAttributes
Use for in-villa chefs. The groceryOnActuals flag pairs with
OnActualsPricing in pricing_config for "Chef (Grocery on Actual)".
Schema
{
"category": "CHEF",
"chefType": "PERSONAL | GROUP | MASTER | SPECIALITY",
"cuisines": ["Indian", "Chinese"],
"durationHours": 8,
"mealCount": 3,
"groceryOnActuals": true
}
Example: "Chef (Grocery on Actual)"
{
"id": "CHEF_GROCERY_ACTUAL",
"name": "Chef (Grocery on Actual)",
"category": "CHEF",
"kind": "SINGLE",
"attributes": {
"category": "CHEF",
"chefType": "PERSONAL",
"cuisines": ["Indian", "Continental"],
"durationHours": 10,
"mealCount": 3,
"groceryOnActuals": true
}
}
Pair this with pricing_config = OnActualsPricing (see
Pricing) so the booking is taken at zero
and grocery cost is reconciled post-stay.
WELLNESS — WellnessAttributes
Use for spa, massage, yoga, ayurveda. Minimal shape — wellness type, duration, whether a certified therapist is bundled.
Schema
{
"category": "WELLNESS",
"wellnessType": "SPA | MASSAGE | YOGA | MEDITATION | AYURVEDA",
"durationMinutes": 60,
"therapistIncluded": true
}
Example: "In-Villa Massage"
{
"id": "MASSAGE_60",
"name": "In-Villa Massage (60 min)",
"category": "WELLNESS",
"kind": "SINGLE",
"attributes": {
"category": "WELLNESS",
"wellnessType": "MASSAGE",
"durationMinutes": 60,
"therapistIncluded": true
}
}
OTHER — OtherAttributes
Escape hatch for categories that haven't been modelled yet. Holds a free-form
properties map. Avoid in new work — add a typed subtype instead. Useful only
to onboard a service quickly and migrate later.
Schema
{
"category": "OTHER",
"properties": {
"freeFormKey": "freeFormValue"
}
}
Example
{
"id": "PHOTOGRAPHER",
"name": "Photo Session",
"category": "OTHER",
"kind": "SINGLE",
"attributes": {
"category": "OTHER",
"properties": {
"hours": 2,
"edited_photos": 25,
"drone_included": true
}
}
}
Constraints (VasConstraints)
Independent of category — applied uniformly across all VAS shapes. Stored in
value_added_service.constraints JSONB. All fields are optional.
Schema
{
"minPax": 4,
"maxPax": 20,
"leadTimeHours": 24,
"seasons": ["WINTER"],
"daysOfWeek": [5, 6, 7],
"availableFrom": "18:00",
"availableUntil": "23:00",
"minBookingValue": 5000.00
}
daysOfWeek follows ISO-8601 (1=Monday … 7=Sunday). seasons is a free-form list
of tags; the booking pipeline checks the booking date against the property's season
calendar.
Example: pin Rain Dance to weekend summers, 6 guests minimum
{
"minPax": 6,
"leadTimeHours": 48,
"seasons": ["SUMMER", "MONSOON"],
"daysOfWeek": [5, 6, 7],
"availableFrom": "16:00",
"availableUntil": "20:00"
}