Listing Autocomplete API
Type-ahead suggestions matched against listing titles and the names of the properties those listings contain. Each suggestion ships with a ready-to-use rawParam you can drop straight into the search request — no separate "resolve selection" step.
Endpoint
GET /api/v1/crs/listings/autocomplete?q={query}&limit={limit}
Also exposed as GET /api/v1/search/listings/autocomplete for callers that already use that prefix.
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | yes | — | User input. Whitespace-only or empty q returns an empty list (not a 400). |
limit | int | no | 10 | Max suggestions returned. Capped at 50. |
%, _, and \ in q are escaped before being wrapped in %…%, so a value like 100% won't degenerate into a full table scan.
Response
{
"suggestions": [
{
"label": "Villa Aurora",
"sublabel": "Anjuna, North Goa",
"matchType": "LISTING",
"listingId": "lst_abc123",
"rawParam": {
"filterName": "listingId",
"filterValues": ["lst_abc123"]
}
},
{
"label": "Sea View Suite",
"sublabel": "Villa Aurora — Anjuna, North Goa",
"matchType": "PROPERTY",
"listingId": "lst_abc123",
"propertyName": "Sea View Suite",
"rawParam": {
"filterName": "listingId",
"filterValues": ["lst_abc123"]
}
}
]
}
Field reference
| Field | Type | Notes |
|---|---|---|
label | string | Primary text the UI renders. Listing title for LISTING hits, property name for PROPERTY hits. |
sublabel | string | locality, city for LISTING hits; listing title — locality, city for PROPERTY hits. Omitted when empty. |
matchType | enum | LISTING or PROPERTY — indicates which column matched q. |
listingId | string | The listing the suggestion resolves to. Same as rawParam.filterValues[0]. |
propertyName | string | Present only on PROPERTY hits. Omitted otherwise. |
rawParam | object | A { filterName, filterValues } object identical in shape to the entries you send to /listings. Designed to be passed through unchanged. |
Ranking is prefix matches first, then alphabetical by the matched label. A property hit and a listing hit that share the same listingId are both returned — the UI can de-duplicate or render both as distinct rows.
Wiring it into search
The whole point of returning rawParam is that the client doesn't have to know about listingId or any other filter name. Just append the chosen suggestion's rawParam to the search request's rawParams array:
// 1. User types, you fetch suggestions
const { suggestions } = await fetch(`/api/v1/crs/listings/autocomplete?q=${q}`).then(r => r.json());
// 2. User picks one
const chosen = suggestions[0];
// 3. Run the search with the rest of the form state + the chosen rawParam
const searchBody = {
rawParams: [
chosen.rawParam, // pass through unchanged
{ filterName: "checkInDate", filterValues: ["2026-06-01"] },
{ filterName: "checkOutDate", filterValues: ["2026-06-04"] },
{ filterName: "adults", filterValues: ["5"] },
],
};
await fetch("/api/v1/crs/listings", { method: "POST", body: JSON.stringify(searchBody) });
curl equivalent:
curl 'https://bard-crs-api.elivaas.com/api/v1/crs/listings/autocomplete?q=villa%20aurora&limit=5'
Location autocomplete
Same idea, different endpoint — type-ahead over distinct city / state / country / locality values that actually appear on listings. Use this when the user is searching for a place rather than a specific property.
GET /api/v1/crs/listings/autocomplete/locations?q={query}&limit={limit}
Also exposed at /api/v1/search/listings/autocomplete/locations.
Response
{
"suggestions": [
{
"label": "Goa",
"matchType": "STATE",
"rawParam": { "filterName": "state", "filterValues": ["Goa"] }
},
{
"label": "North Goa",
"matchType": "CITY",
"rawParam": { "filterName": "city", "filterValues": ["North Goa"] }
},
{
"label": "Anjuna",
"matchType": "LOCALITY",
"rawParam": { "filterName": "locality", "filterValues": ["Anjuna"] }
}
]
}
matchType | rawParam.filterName |
|---|---|
CITY | city |
STATE | state |
COUNTRY | country |
LOCALITY | locality |
sublabel, listingId, and propertyName are omitted on location hits — only label, matchType, and rawParam are returned.
Values are pulled only from rows in the listing table, so a suggested city/state/country/locality is guaranteed to have at least one listing. No more dead-end picks that resolve to zero results.
q, limit, escaping, ordering (prefix matches first), and empty-q handling all behave exactly the same as the main autocomplete endpoint above.
Behavior notes
- Empty / whitespace
q→{ "suggestions": [] }with HTTP 200. Lets a debounced type-ahead UI fire on every keystroke without producing 400s on the first character. - No matches → empty
suggestionsarray, not 404. limitis clamped to[1, 50]. Values outside that range are silently coerced.- Search is case-insensitive (PostgreSQL
ILIKE). - One listing may appear twice — once as a
LISTINGhit and once as aPROPERTYhit — when both its title and a property name matchq. UselistingIdto de-duplicate if desired; both rows share the samerawParam.
rawParam and not just listingId?Returning the full rawParam object means callers can append it without knowing the filter name, escaping rules, or array shape. If a future suggestion type needs to filter by something other than listingId (e.g. tags for a "Pet Friendly" suggestion), only the server changes — the client keeps appending whatever rawParam it received.