---
title: "Explore Search"
method: GET
path: "/explore/search"
tags: ["explore"]
---

# Explore Search

`GET /explore/search`

Corpus-wide place search (CORPUS_SEARCH_DESIGN.md §4.2). lat/lng bias
to a ~50km box with auto-widen.

`mode` defaults to the shipping product one: hybrid, so a client that
sends q/limit/sid only (all of them do) gets the BM25 + ANN fusion. It is
ungated — the admin preview's eval toggle picks `bm25`/`semantic`, and
neither costs more embedding budget than the default already spends.

`preview` stays admin-only — it marks a serve as excluded from training
data, so honouring it from arbitrary callers would let real user searches
opt themselves out. Coerced rather than 400'd: it is absent from every
shipping client's query string, so a request carrying it is hand-rolled,
not a client that needs an error.

## Query parameters

- `q` string, required
- `lat` number, nullable
- `lng` number, nullable
- `label` string, nullable
- `limit` integer
- `preview` boolean
- `mode` 'bm25' | 'semantic' | 'hybrid'
- `sid` string, nullable

## Headers

- `x-client-build` string, nullable

## Response `200`

Successful Response

- FeedResponse
  - `request_id` string, required
  - `anchor_label` string, nullable
  - `items` ExploreItem[], required
    - `place_id` string, required
    - `name` string, required
    - `category` string, nullable
    - `city` string, nullable
    - `country_code` string, nullable
    - `lat` number, nullable
    - `lng` number, nullable
    - `image_url` string, nullable
    - `image_thumbhash` string, nullable
    - `rating` number, nullable
    - `num_reviews` integer, nullable
    - `description` string, nullable
    - `michelin_award` string, nullable
    - `michelin` MichelinDistinction — A place's Michelin Guide distinction, structured. Canonical rationale — the TS mirror and the renderers point here rather than restating it. Rides alongside `michelin_award` rather than replacing it: that field is a pre-rendered label ("★★★"), and a label cannot drive iconography. An award string we don't recognise yields NO distinction (we never paint a mark we can't justify) while still surfacing verbatim in `michelin_award`, so a new tier degrades to text rather than disappearing.
      - `tier` 'stars' | 'bib_gourmand' | 'selected', required
      - `stars` integer, nullable
      - `green_star` boolean
      - `url` string, nullable
      - `cuisine` string, nullable
    - `source` string, required
    - `score` number, required
    - `position` integer, required
    - `reason` string, nullable
    - `hydration_pending` boolean
    - `sources` string[]
    - `kind` 'place' | 'stay'
    - `stay` StayFacets — What a Michelin Guide hotel carries that an ordinary place doesn't. Same rationale as `MichelinDistinction`, one category later: `key_label` is the Guide's own pre-rendered string ("Two MICHELIN Keys"), and a label cannot drive iconography, so `tier`/`keys` ride beside it. A tier we don't recognise yields no structured distinction while still surfacing verbatim in `key_label` — a new tier degrades to text rather than disappearing. A sub-object rather than flat columns on `ExploreItem`: otherwise every future category (a tour, a tasting menu) adds its own nullable fields to the type EVERY card in the app carries.
      - `key_label` string, nullable
      - `tier` 'keys' | 'selected', nullable
      - `keys` integer, nullable
      - `guide_url` string, nullable
      - `editorial` string, nullable
      - `check_in` string, date, nullable
      - `check_out` string, date, nullable
    - `distance_km` number, nullable
    - `distance_from` 'you' | 'hotel', nullable
  - `next_offset` integer, nullable
  - `title` string, nullable
  - `rail_kind` 'bridge', nullable
  - `cluster_index` integer, nullable

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-08** `6ea721316081` — 1 breaking
  - for the `query` request parameter `mode`, default value was changed from `bm25` to `hybrid`
- **2026-09-04** `eb694aedc555` — 3 info
  - added the optional property `cluster_index` to the response with the `200` status
  - added the optional property `rail_kind` to the response with the `200` status
  - added the optional property `title` to the response with the `200` status

[Change history](https://skmtc.dev/stardrift/apis/fastapi/changes/explore/search/get.md)

---

[API](https://skmtc.dev/stardrift/apis/fastapi.md) · [All operations](https://skmtc.dev/stardrift/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc.dev/stardrift/apis/fastapi/revisions/ae2c9c9f8e5d?raw)
