---
title: "Generate Plan Endpoint"
method: POST
path: "/trips/{trip_id}/plans/{plan_id}/generate"
tags: ["plan"]
---

# Generate Plan Endpoint

`POST /trips/{trip_id}/plans/{plan_id}/generate`

Generate (or regenerate) a plan from current itinerary items.

## Path parameters

- `trip_id` string, required
- `plan_id` string, required

## Request body

- PlanGenerateRequest — Request for POST /plans/{plan_id}/generate.
  - `force` boolean

## Response `200`

Successful Response

- PlanItemResponse — Single plan with full data.
  - `id` string, required
  - `plan` PlanContent, required — The full plan document stored in the DB.
    - `schema_version` integer
    - `migrated_at` string, date-time, nullable
    - `preamble` union[]
      - union
        - ActivityRefEntry — Standalone attraction card — a reference to an activity in the itinerary. In v2, a ref can carry a plain-text `note` annotation and an optional `time` — both user-authored on the attraction card.
          - `id` string, required
          - `type` 'activity_ref'
          - `item_id` string, required
          - `note` string, nullable
          - `time` string, time, nullable
        - ActivityGroupEntry — A group of activity refs rendered as a single carousel/cluster card.
          - `id` string, required
          - `type` 'activity_group'
          - `item_ids` string[]
          - `title` string, nullable
          - `caption` string, nullable
          - `time` string, time, nullable
        - FlightRefEntry — One SEGMENT of a flight leg placed in the day's entry flow. Granularity is the individual takeoff/landing: a connecting leg seeds one entry per hop, each on the day of THAT hop's departure date (so an after-midnight connection sits on the next day). ``segment_index`` defaults to 0, which keeps pre-segment entries valid — an old per-leg entry reconciles as the leg's first hop and the remaining segments seed fresh. The segment's data lives in the itinerary (``journey_id`` + ``leg_index`` + ``segment_index``); only the position within the day is user-owned. The server seeds and reconciles these entries inside ``validate_plan`` (see ``api.plan.service._reconcile_flight_entries``) so a flight is a first-class, user-orderable entry rather than a card derived at render time. ``time`` is a server-stamped copy of the segment's local departure ``"HH:MM"`` so time-based sorting treats a flight like any other timed entry. It is ``None`` for a date-only import (departure == arrival, zero duration) so the day sorts it to the top rather than at a bogus 00:00. Because the position is user-owned once seeded, ``set_entry_time`` and ``remove_entry`` are rejected for this type (time comes from the itinerary; a removed flight would silently reappear on the next validate) while ``move_entry`` is allowed — repositioning is the whole point.
          - `id` string, required
          - `type` 'flight_ref'
          - `journey_id` string, required
          - `leg_index` integer, required
          - `segment_index` integer
          - `time` string, time, nullable
        - NoteEntry — Freeform text section. The `nodes` field stores the raw Lexical serialized node tree for lossless round-tripping of rich text. `content` always carries a plain-text fallback. Inline refs in both `content` and `nodes` are activity-only in v2.
          - `id` string, required
          - `type` 'note'
          - `content` string
          - `nodes` object, nullable
          - `time` string, time, nullable
        - TimeEntry — Time marker as a standalone entry.
          - `id` string, required
          - `type` 'time'
          - `time` string, time, required
        - EclipseSiteEntry — Eclipse viewing-site node — identity + coordinates only. On an eclipse-mode plan the eclipse day carries this single node. The web client renders it as a live eclipse-circumstances "mini-dossier" card: every contact time, totality duration, sun altitude, horizon margin, and cloud figure is computed CLIENT-SIDE from the coordinates (the existing `/eclipse/point` machinery). The backend supplies ONLY identity + coords — it never writes circumstance data into the plan. Two forms mirror how the site was chosen: - a known trip place: `ref_id` is the itinerary activity ULID it points at, and `latitude`/`longitude` are copied from that place's record at parse/serialize time (so the card is self-contained even if the place is later removed from the trip); - raw coordinates from "plan around this point": `ref_id` is None and the pair is the site. Discriminator note: `type` is the PlanEntry union tag — every consumer (backend union, payload sanitizer, revision diff, streaming deltas, the web and mobile entry switches) keys entries on `type`, so it MUST be present. `kind` duplicates it for the frontend card contract. They are always equal.
          - `id` string, required
          - `type` 'eclipse_site'
          - `kind` 'eclipse_site'
          - `label` string, required
          - `latitude` number, required
          - `longitude` number, required
          - `ref_id` string, nullable
    - `days` PlanDay[] — unresolved $ref
  - `conflicts` PlanConflict[]
    - `type` 'overlap' | 'tight_connection' | 'date_mismatch', required
    - `item_ids` string[], required
    - `day_ids` string[], required
    - `message` string, required
    - `severity` 'error' | 'warning'
  - `version` integer, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/stardrift/apis/fastapi.md) · [All operations](https://skmtc.dev/stardrift/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/stardrift/fastapi/revisions/00746572572a/schema)
