---
title: "Search nearby places by freeform query"
method: POST
path: "/places"
---

# Search nearby places by freeform query

`POST /places`

## Request

- Base URL: `https://localselections.com/api/v1`
- URL: `https://localselections.com/api/v1/places`
- Auth: API key in header `X-API-Key`

Stage 196: real nearby places for an arbitrary text query (e.g. "late night tacos", "dog-friendly patio") and an optional Google Places `includedType` filter — the same underlying integration /search wraps (searchNearbyPlacesByType: Overture/Geoapify's free local databases tried first, Stage 191, falling back to a real, budget-circuit-broken Google Places call for whatever gap remains), but without /search's restriction to this app's own enumerated cuisine/activity category ids. Each result carries a `detailUrl` to a real, permanent LocalSelections page when the place is confidently matched and not an unresolved duplicate of another listing (null otherwise — see the NearbyPlace schema). Requires the `places` scope. Costs 1 unit of the key's rate-limit budget per call. Stage 198: on top of that shared per-minute budget, a free-tier key is separately capped at 100 calls/day to this endpoint specifically (see the 429 response) — commercial use is exempt (see GET /introspect's `commercial` field and the API Terms of Use).

## Request body

- object
  - `query` string, required: Freeform search text, e.g. "sushi", "hiking trail", "dog-friendly patio".
  - `includedType` string: Optional Google Places type filter, e.g. "restaurant", "park", "bowling_alley".
  - `lat` number, required
  - `lng` number, required

## Response `200`

Nearby places for the requested query.

- object
  - `places` object[], required
    - `id` string, required
    - `name` string, required
    - `distanceMiles` number, nullable
    - `address` string, nullable
    - `mapsUri` string, uri, required
    - `trending` object, nullable: Null only if the trending lookup itself failed — otherwise always present, with 0s when there's no real history yet.
      - `contributorCount` integer, required: Distinct users who were shown this place as their actual narrowed/won result, all-time.
      - `likedCount` integer, required: Subset of contributorCount who also left a real "liked" outcome vote.
      - `recentContributorCount` integer, required: Subset of contributorCount within the last 7 days.
    - `sponsored` boolean: True when this place has an active, admin-approved vendor boost (Stage 28) — always ranked among places that would organically appear in this search, never inserted separately.
    - `vendorDetails` object, nullable: Stage 185: the claimed vendor's own submitted hours/phone/website/menu highlights (Stage 184), when their listing is approved and at least one field has been submitted. Null otherwise. Takes display precedence over any Overture/Geoapify-sourced equivalent value for the same place.
      - `hours` string, nullable, required
      - `phone` string, nullable, required
      - `website` string, uri, nullable, required
      - `menuHighlights` string, nullable, required
    - `origin` 'overture' | 'geoapify' | 'google_places', required: Stage 191: which real source this place actually came from. Overture (local_selections.places, confidence-floored + deduped) and Geoapify (local_places) are tried first, free of any real API cost; Google Places is the fallback for whatever gap remains.
    - `detailUrl` string, uri, nullable: Stage 196: a real, permanent LocalSelections detail page for this exact place, reusing that page's own indexability rules (never a link to a page that would itself noindex, 301 elsewhere, or not exist). Null for every `google_places`-origin result (no permanent page exists for one) and for a local result that hasn't cleared the stricter indexing confidence floor or has an unresolved cross-source duplicate. Only present on /places (not /search, which predates this field).

## Other responses

- `400`: The request body failed validation.
- `401`: Missing or invalid API key.
- `403`: The API key does not carry the scope this endpoint requires (see the "Requires the ... scope" note on each path, or call GET /introspect to see a key's real granted scopes).
- `429`: Rate limit exceeded for this API key. The budget is 30 units/60s for keys owned by an unsubscribed account, 120 units/60s for a subscribed account's keys (Stage 60) — shared across every endpoint, but each endpoint spends a different number of units per call rather than always 1 (Stage 104): search/groups_host/groups_join/places/movies cost 1, surprise_me/events cost 2, spin costs 3, bracket costs 4. Call GET /introspect to see a key's real remaining budget before it runs out. Separately, these three endpoints also enforce their own free-tier-only daily ceiling per key (`error: "daily_limit_exceeded"`) — resets at UTC midnight, and does not apply to a commercial-tier key. Contact support@localselections.com for commercial/high-volume access.
- `502`: An upstream data source failed or returned nothing usable.

---

[API](https://skmtc.dev/localselections/apis/localselections-public-api.md) · [All operations](https://skmtc.dev/localselections/apis/localselections-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/localselections/apis/localselections-public-api/revisions/a24e9598add4?raw)
