Property Searches

Part of the Content API. Saved property searches drive the listings shown on content pages, area pages and the home page, and search links (/search/results/?...) appear throughout site content. This page covers the criteria format, how to discover the fields a site accepts, and a procedure for building searches that actually filter.

Criteria format

Criteria are an object of {"field_id": ["value", ...]}. The same shape is accepted by the search and search_field_defaults fields on pages, by /api/v2/search/ as a query string, and in /search/results/?... links.

  • Keys are field ids from the site’s search field catalog; values are always arrays of strings, and never empty — every field present must have at least one value.
  • Map areas: polygon takes one string of lat,lng pairs joined by ; (latitude first, decimal degrees to about 5 places, at least 3 points, closed by repeating the first point), e.g. {"polygon": ["26.94600,-80.09400;26.94600,-80.07000;26.92000,-80.07000;26.94600,-80.09400"]}. A polygon replaces the location fields: city, zip, subdivision and the like are dropped from a search that has one.
  • Range fields take <id>_min / <id>_max keys, e.g. {"list_price_max": ["800000"]}.
  • Multi-select fields take the stored value, not the display label: "con", not "Condo". Repeat values for several, e.g. {"type": ["res", "con"]}.
  • Boolean fields take ["True"] or ["False"].
  • Only include fields relevant to the search — an omitted field means “any”.

Discovering a site’s search fields

GET/search_forms/api/dump_uberform_fields.json
GET/search_forms/api/advanced_search_form.json
GET/api/v2/search/metadata/
GET/api/v2/search/autocomplete-options/
GETPOST/api/v2/search/

Search fields and their values are site-specific (they follow the site’s MLS and its search form setup). These endpoints on the site’s public search stack let a tool explore them before writing a criteria object or a search link into content. They sit outside /api/v3/ and need no API key.

  • GET /search_forms/api/dump_uberform_fields.json is the search field catalog: every field the site’s searches accept, including ones its search forms don’t show (such as subdivision or school on many MLSs). Each entry has id (the criteria key), label, widget, flags and choices as {"val", "label"}. min_max_field widgets take <id>_min / <id>_max; send the choice val, not the label; fields flagged dynamic (city, subdivision, school…) take their values from the MLS and list none here, so use the autocomplete index below. polygon is accepted but not listed (see Criteria format). AI assistants connected over MCP get the same catalog from the search_fields tool.
  • GET /search_forms/api/advanced_search_form.json returns the advanced search form as {"primary": [...], "secondary": [...]}. Each entry is one field: attr (the criteria key), label, widget_type, default_value, value (one row per checkbox value, e.g. type=res), dependent_fields, and choices as [value, label] pairs. City and subdivision choice lists follow the site’s default county.
  • GET /api/v2/search/autocomplete-options/ returns the site’s autocomplete index as [{"field", "value"}, ...] — every value the site knows for a field across all counties and cities, beyond the form’s default lists.
  • GET /api/v2/search/metadata/?<criteria> echoes {"description", "criteria"}: the site’s own parse of a query string. The site’s search pages silently drop unknown criteria (the page “works” but matches everything), so compare what you sent with the echoed criteria — any key missing there was ignored.
  • GET /api/v2/search/?<criteria> runs the search; page, per_page, fields, include_description and sort_highest / sort_lowest / sort_latest / sort_oldest / sort_relevance control the response, and the match count is in the X-Total-Count header.

From the command line: geekcli search fields, geekcli search choices <field> with geekcli (see the geekcli guide).

Building criteria that work

These are the rules our own search-building tools follow. They exist because the failure mode of a bad search is silent: the page renders, the listings look plausible, and nothing is actually being filtered.

  • Only use field ids the site lists. Never guess or invent a field id. The site’s search pages ignore unknown keys silently; the /api/v3/ search field rejects them with a 422 naming each one.
  • Verify values before using them. Values are site-specific: a city, subdivision or school name that exists on one site may not exist on another, and a misspelled value matches nothing. Confirm it in the catalog’s choices or the autocomplete index first. The /api/v3/ search field fixes capitalization to the MLS spelling and warns about values it does not know (unknown_value, with suggestions), but a correct value is still better than a warning. Where fields depend on each other (subdivisions within a city, schools within an area), check the value against the rest of your criteria, not just globally.
  • Map only what actually maps. When a request mentions something no field covers — “jacuzzi”, “good schools”, “nice neighborhood” — skip it rather than approximating with an unrelated field, and say so in whatever notes or summary you give the user. A wrong filter is worse than a missing one.
  • Set the property type when one is implied. “Homes”, “condos”, “townhouses”, “land” all map to the type field; include it whenever the request names one.
  • Check the parse. Before writing a search link into content, run the criteria through metadata/ and confirm every key came back. A link built from ignored keys shows every listing on the site.

Test before you attach

A criteria object that validates can still be a bad search. Before saving it or attaching it to a page:

  1. Run it (GET /api/v2/search/?<criteria>&per_page=1) and read the match count from X-Total-Count.
  2. Zero results? Broaden stepwise while keeping the request’s core intent: drop the least important filters first, widen price ranges, reduce bedroom/bathroom minimums, expand the location to nearby areas — re-verifying values as you go. Re-test after each change and keep the best version rather than the last one. If nothing helps after a few rounds, report that honestly instead of shipping a search that shows nothing.
  3. Suspiciously large counts? If the count is close to the site’s whole inventory, the criteria probably are not filtering — check for ignored keys with metadata/.
  4. Sanity-check the description. metadata/ returns the site’s own human-readable description of the search; if it does not read like what was asked for, the criteria do not mean what you think.

From the command line: geekcli search check city=Riverside, geekcli search run city=Riverside with geekcli (see the geekcli guide). search check flags criteria the site ignored.

POST /api/v2/search/?<criteria> stores the search and returns {"search_id", "search_id_int"}. The base36 search_id is the short id that appears in the site’s own URLs; identical criteria reuse the same saved search.

  • Link to results with /search/results/<search_id>/ (or /search/results/?<criteria> without saving), and the map view with /map_search/results/<search_id>/1/.
  • Attach the search to a page through the search field on content pages, area pages and the home page — it accepts the search_id, the numeric id, or a criteria object directly (the API saves or reuses a search for you).
  • search_field_defaults takes the same values to pre-fill a page’s search form without filtering its listings.

From the command line: geekcli search url --save city=Riverside, geekcli pages update <page> --search-criteria city=Riverside with geekcli (see the geekcli guide).