Skip to main content

Listing Autocomplete API

Type-ahead suggestions matched against listing titles. Property names are not searched — a listing is offered once, under its own title. 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​

NameTypeRequiredDefaultDescription
qstringyes—User input. Whitespace-only or empty q returns an empty list (not a 400).
limitintno10Max 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": "Villa Serena",
"sublabel": "Vagator, North Goa",
"matchType": "LISTING",
"listingId": "lst_def456",
"rawParam": {
"filterName": "listingId",
"filterValues": ["lst_def456"]
}
}
]
}

Field reference​

FieldTypeNotes
labelstringPrimary text the UI renders — the listing title.
sublabelstringlocality, city. Omitted when empty.
matchTypeenumLISTING — the listing title matched q.
listingIdstringThe listing the suggestion resolves to. Same as rawParam.filterValues[0].
rawParamobjectA { 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 title. Each listing appears at most once.


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"] }
}
]
}
matchTyperawParam.filterName
CITYcity
STATEstate
COUNTRYcountry
LOCALITYlocality

sublabel and listingId 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 suggestions array, not 404.
  • limit is clamped to [1, 50]. Values outside that range are silently coerced.
  • Search is case-insensitive (PostgreSQL ILIKE).
  • Each listing appears at most once. Only listing.title is matched, so a listing whose property names also contain q still yields a single row.

Why 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.