Listing Search API
Search listings with structured filters. Context-aware pricing applied automatically.
Structured Search
POST /api/v1/crs/listings
Content-Type: application/json
{
"rawParams": [
{ "filterName": "channelId", "filterValues": ["B2C"] },
{ "filterName": "city", "filterValues": ["Goa"] },
{ "filterName": "checkInDate", "filterValues": ["2026-05-15"] },
{ "filterName": "checkOutDate", "filterValues": ["2026-05-17"] },
{ "filterName": "adults", "filterValues": ["4"] },
{ "filterName": "children", "filterValues": ["2"] },
{ "filterName": "propertyType", "filterValues": ["Villa"] },
{ "filterName": "petFriendly", "filterValues": ["true"] },
{ "filterName": "maxPrice", "filterValues": ["20000"] }
]
}
Response
{
"singleListings": [
{
"listingId": "lst_abc",
"title": "Nimishka Villa",
"coverPicture": "https://cdn.elivaas.com/villa.jpg",
"propertyType": "VILLA",
"brand": "priv\u00e9",
"badges": [
{ "label": "Best Seller", "type": "BEST_SELLER", "placement": null },
{ "label": "In Demand", "type": "IN_DEMAND", "placement": null },
{ "label": "High Margin", "type": "HIGH_MARGIN", "placement": null }
],
"adults": 6,
"bedrooms": 3,
"bathrooms": 2,
"unitCount": 4,
"locality": "Chattyan",
"city": "Kasauli",
"state": "Himachal Pradesh",
"rating": 4.6,
"tags": ["Pvt Pool", "Garden Area", "Scenic Views", "Game Room"],
"selectedProperties": [
{ "propertyId": "prop_1", "title": "Deluxe Room", "quantity": 2 }
],
"price": {
"total": 32800,
"perNight": 16400,
"totalBeforeDiscount": 38000,
"totalSaving": 5200,
"discountPercent": 14
},
"offerTitles": ["10% off with Visa"],
"cancellationPlans": [
{
"id": "plan_flex",
"name": "Flexible",
"planType": "FLEXIBLE",
"cancellationSummary": "Free cancellation till 7 days before check-in",
"hasDiscount": false,
"totalAfterDiscount": 32800,
"perNightAfterDiscount": 16400
}
]
}
],
"combinations": []
}
Available Filters
Generated from com.elivaas.common.dto.listing.FilterCatalog \u2014 do not edit by hand. Run ./gradlew :common:generateFilterDocs after adding a filter.
| filterName | Type | Example | Description |
|---|---|---|---|
channelId | string | B2C | Sales channel (B2C, B2B) |
city | string[] | ["Goa"] | Filter by city (case-insensitive) |
state | string[] | ["Himachal Pradesh"] | Filter by state (case-insensitive) |
country | string[] | ["India"] | Filter by country (case-insensitive) |
locality | string[] | ["Kasauli"] | Filter by locality (case-insensitive) |
listingId | string[] | ["lst_abc"] | Specific listing IDs (case-sensitive, opaque) |
brand | string[] | ["privé"] | Filter by brand (case-insensitive) |
propertyType | string[] | ["Villa"] | Villa, Apartment, Resort (case-insensitive) |
tags | string[] | ["Scenic Views"] | Required tags (post-filter, in-memory) |
amenities | string[] | ["Pool","Wifi"] | Required amenities (post-filter) |
roomView | string[] | ["Sea View","Mountain View"] | Matches property_views.view_name via property_view_mappings (case-insensitive) |
bhk | string[] | ["2 BHK","3"] | Leading int parsed; matches properties.total_bedrooms |
houseRules | string[] | ["smoking","alcohal"] | Matches property_rules.name via property_rule_mappings (case-insensitive). status not yet applied |
searchQuery | string | Nimishka | Text search on listing/property name (ILIKE) |
query | string | villa in goa for 4 adults next weekend | Natural-language query. Triggers AI intent extraction in the search controller, which synthesizes additional rawParam filters (city, dates, propertyType, etc.) and merges them into this request. Echoed back via response.rawParams so the UI can render selected filters. |
minPrice | int | 5000 | Min per-night price |
maxPrice | int | 20000 | Max per-night price |
minBedrooms | int | 2 | Min total bedrooms (sum of properties.total_bedrooms weighted by quantity) |
maxBedrooms | int | 5 | Max total bedrooms |
minBathrooms | int | 1 | Min bathrooms (column pending — currently no-op) |
maxBathrooms | int | 4 | Max bathrooms (column pending — currently no-op) |
minAdults | int | 2 | Min adult capacity (sum-weighted) |
maxAdults | int | 10 | Max adult capacity |
minRating | double | 4.0 | Min rating (column pending — currently no-op) |
adults | int | 4 | Required adults |
children | int | 2 | Required children |
infants | int | 1 | Required infants |
pets | int | 1 | Required pets |
checkInDate | string | 2026-06-01 | YYYY-MM-DD |
checkOutDate | string | 2026-06-04 | YYYY-MM-DD |
petFriendly | boolean | true | Pet-friendly only |
stressProperties | boolean | true | Stressed/deal properties |
allowCombination | boolean | true | Enable multi-listing groups |
maxListingsInGroup | int | 3 | Max listings per group (default 3) |
includeSingleListings | boolean | true | Include single-fit listings in result |
proximityMeters | double | 500 | Radius for combination grouping |
anchorLat | double | 15.5 | Anchor latitude for proximity |
anchorLon | double | 73.7 | Anchor longitude for proximity |
minSingleResults | int | 3 | Trigger combinations only below this many singles |
page | int | 1 | 1-based page index for singleListings |
size | int | 10 | Page size for singleListings |
bev | string | v_abc123 | Visitor cookie (auto-injected) |
partner_id | string | ptn_xyz | Partner (auto-injected or explicit) |
utm_source | string | google | UTM source |
utm_medium | string | cpc | UTM medium |
utm_campaign | string | summer-sale | UTM campaign |
Pagination
page and size are filters like any other — pass them inside rawParams and the response gains four pagination fields next to singleListings. Omit them and the response is byte-identical to a pre-pagination caller.
{
"rawParams": [
{ "filterName": "city", "filterValues": ["Goa"] },
{ "filterName": "checkInDate", "filterValues": ["2026-06-01"] },
{ "filterName": "checkOutDate", "filterValues": ["2026-06-04"] },
{ "filterName": "adults", "filterValues": ["5"] },
{ "filterName": "page", "filterValues": ["1"] },
{ "filterName": "size", "filterValues": ["10"] }
]
}
Response (additions only):
{
"singleListings": [ /* sliced to the requested window */ ],
"page": 1,
"size": 10,
"totalSingleListings": 42,
"totalPages": 5
}
pageis 1-based. Values< 1are coerced to1.sizeis required alongsidepageto enable pagination. If either is missing, the legacy unpaged behavior runs and the four pagination fields are omitted from the response.- Pagination is purely presentational — it slices the sorted
singleListingsafter sorting, and never changes whethercombinedGroupsare built. The combination fallback still triggers on the total number of single fits, not the paginated slice. - Out-of-range pages return an empty
singleListingswith accuratetotalPages/totalSingleListings, never a 4xx.
Sorting
Single-fit listings are sorted by:
- Occupancy fit (primary) \u2014 total selected adult+child capacity minus required, smallest first. A 2-adult villa beats a 10-adult villa for an
adults=2query. - Static cost (tie-breaker) \u2014
price/100 + (10 - rating) * 20. Cheaper, higher-rated wins.
Listing badges
Listings carry an array of Badge { label, type, placement }. Types are populated from the listing_badge table by background jobs (e.g. BEST_SELLER, IN_DEMAND, HIGH_MARGIN). Rows past expires_at are excluded automatically. placement is reserved for future server-driven UI placement; currently null.
On the website (POST /api/v1/listings/search), traffic context is auto-injected from the bev cookie.
You don't need to pass partner_id or utm_source — the backend resolves it automatically.