---
title: "Nl Query"
method: POST
path: "/api/v1/planner/query"
tags: ["planner"]
---

# Nl Query

`POST /api/v1/planner/query`

## Request

- Base URL: `https://yondry.app` (the document declares no server; this is the origin it was published from)
- URL: `https://yondry.app/api/v1/planner/query`
- Auth: HTTP bearer

Natural-language planner endpoint.

Accepts a free-text query and returns 1-2 structured itinerary plans.
The response is persisted so the user can retrieve their history and
submit feedback on individual plans.

Steps (inside NLPlannerService.plan):
  1. Claude Haiku extracts structured intent from the query
  2. User's library places are haversine-filtered; weather fetched concurrently
  3. Google Places gap-fills types with fewer than 2 library results
  4. Claude Sonnet generates a single coherent day plan (returned as a
     one-item list for API backward compatibility)

Raises:
  422  if Claude Haiku cannot parse the query into structured intent
  500  if Claude Sonnet cannot produce a valid plan

## Request body

- PlannerRequest: Request body for POST /planner/query.
  - `query` string, required
  - `origin_lat` number, nullable
  - `origin_lon` number, nullable
  - `radius_km` number
  - `departure_time` string, date-time, nullable
  - `travel_mode` string
  - `optimize_for_return` boolean
  - `vibes` string[]
  - `companion` string
  - `must_include_places` MustIncludePlace[]
    - `name` string, required
    - `category` string, required
    - `lat` number, required
    - `lon` number, required
    - `google_place_id` string, nullable
    - `is_free` boolean, nullable
  - `prefer_discovery` boolean
  - `discovery_weight` number, nullable
  - `zone_lat` number, nullable
  - `zone_lon` number, nullable
  - `zone_name` string, nullable
  - `previous_plan` Plan: One complete itinerary option returned by Claude Sonnet.
    - `label` string, required
    - `stops` PlanStop[], required
      - `place_id` string, nullable
      - `name` string, required
      - `category` string, required
      - `why` string, required
      - `suggested_duration_minutes` integer, required
      - `is_discovery` boolean
      - `is_meal` boolean
      - `lat` number, nullable
      - `lng` number, nullable
      - `admission_price` string, nullable
      - `alternative_stops` AlternativeStop[]
        - `name` string, required
        - `category` string, required
        - `why` string, required
      - `travel_leg` TravelLegOut
        - `from_stop` string, required
        - `to_stop` string, required
        - `distance_km` number, required
        - `travel_mins` integer, required
        - `travel_mode` string, required
      - `arrival_time` string, date-time, nullable
      - `transition_to_next` TransitionSegment: Travel segment between two consecutive stops within the zone.
        - `mode` string, required
        - `minutes` integer, required
        - `distance_km` number, required
        - `is_estimated` boolean
    - `summary` string, required
    - `conflicts` PlanningConflictOut[]
      - `type` string, required
      - `stop_name` string, required
      - `detail` string, required
      - `suggestion` string, required
    - `total_duration_mins` integer, nullable
    - `plan_notes` string
    - `arrival_segment` JourneySegment: Arrival or return leg between the user's start location and the destination zone.
      - `from_location` string, required
      - `to_location` string, required
      - `mode` string, required
      - `minutes` integer, required
      - `depart_time` string, date-time, required
    - `return_segment` JourneySegment: Arrival or return leg between the user's start location and the destination zone.
      - `from_location` string, required
      - `to_location` string, required
      - `mode` string, required
      - `minutes` integer, required
      - `depart_time` string, date-time, required
    - `buffer_minutes` integer
    - `travel_note` string
  - `previous_plan_id` string, uuid, nullable
  - `follow_up_query` string, nullable
  - `timezone` string, nullable
  - `language` string
  - `campaign_slug` string, nullable
  - `anchor_place_id` string, nullable

## Response `201`

Successful Response

- PlannerResponse: Response for POST /planner/query.
  - `generated_plan_id` string, uuid, required
  - `query` string, required
  - `intent` PlannerIntent, required: Structured intent extracted from a free-text query by Claude Haiku.
    - `moods` string[]
    - `place_types` string[]
    - `duration_hours` number, nullable
    - `companions` string[]
    - `time_of_day` string, nullable
    - `budget` string, nullable
    - `excluded_place_types` string[]
    - `prefer_discovery` boolean
    - `vibe_conflicts` string[]
    - `planning_mode` union
      - 'local' | 'known_day_trip' | 'discovery_day_trip' | 'away_trip'
      - string
    - `destination` string, nullable
    - `discovery_constraints` DiscoveryConstraints: Spatial/vibe constraints when the user wants us to find a destination.
      - `max_travel_minutes` integer, required
      - `vibe_keywords` string[]
      - `must_haves` string[]
      - `exclusions` string[]
    - `start_location_type` union
      - 'home' | 'custom' | 'current_location' | 'future_location'
      - string
    - `arrival_transport` string, nullable
    - `local_transport` string, nullable
    - `duration_minutes` integer, nullable
    - `start_time` string, date-time, nullable
    - `must_include_meal` boolean
    - `constraints_inferred` string[]
    - `ambiguity_flags` string[]
    - `first_time_visitor` boolean, nullable
    - `named_landmarks` string[]
  - `plans` Plan[], required
    - `label` string, required
    - `stops` PlanStop[], required
      - `place_id` string, nullable
      - `name` string, required
      - `category` string, required
      - `why` string, required
      - `suggested_duration_minutes` integer, required
      - `is_discovery` boolean
      - `is_meal` boolean
      - `lat` number, nullable
      - `lng` number, nullable
      - `admission_price` string, nullable
      - `alternative_stops` AlternativeStop[]
        - `name` string, required
        - `category` string, required
        - `why` string, required
      - `travel_leg` TravelLegOut
        - `from_stop` string, required
        - `to_stop` string, required
        - `distance_km` number, required
        - `travel_mins` integer, required
        - `travel_mode` string, required
      - `arrival_time` string, date-time, nullable
      - `transition_to_next` TransitionSegment: Travel segment between two consecutive stops within the zone.
        - `mode` string, required
        - `minutes` integer, required
        - `distance_km` number, required
        - `is_estimated` boolean
    - `summary` string, required
    - `conflicts` PlanningConflictOut[]
      - `type` string, required
      - `stop_name` string, required
      - `detail` string, required
      - `suggestion` string, required
    - `total_duration_mins` integer, nullable
    - `plan_notes` string
    - `arrival_segment` JourneySegment: Arrival or return leg between the user's start location and the destination zone.
      - `from_location` string, required
      - `to_location` string, required
      - `mode` string, required
      - `minutes` integer, required
      - `depart_time` string, date-time, required
    - `return_segment` JourneySegment: Arrival or return leg between the user's start location and the destination zone.
      - `from_location` string, required
      - `to_location` string, required
      - `mode` string, required
      - `minutes` integer, required
      - `depart_time` string, date-time, required
    - `buffer_minutes` integer
    - `travel_note` string
  - `weather` string, nullable, required
  - `generated_at` string, date-time, required
  - `plans_used` integer, nullable
  - `plans_limit` integer, nullable

## Other responses

- `422`: Validation Error

---

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