# PRS — Luna Payload Superset (what to always send)

For Voyager / Luna. **Send one rich payload per itinerary item; PRS extracts what each supplier needs.** You don't branch per supplier — you pick the vertical with `service_type`, fill in everything you have, and PRS hands each supplier its preferred fields. This page is the **union of every field across every vertical**, plus the required minimums.

Companion: the per-vertical request/response detail lives in [`SERVICES_INTEGRATION.md`](./SERVICES_INTEGRATION.md) (served at `/guide/services`). Endpoint, auth, sync/async, and the Luna service-type codes (RST/GUI/ENT/ATR/MTC/FRY) are documented there.

---

## The one rule that prevents "no_coverage"

> **For every place/location, send every identifier you have.**

PRS routes each to the supplier that uses it:

| Locator | Used by |
|---|---|
| `geo` `{ latitude, longitude }` | geo suppliers — Daytrip (transfer/chauffeur), Amadeus (tour/museum tickets) |
| `city_code` (IATA-like, e.g. `LON`) | city suppliers — Viator, Daytrip Day Trips, Tiqets, hotel bedbanks |
| `country_code` (ISO 3166-1 alpha-2, e.g. `GB`) | tour, restaurant, hotel (and a useful hint everywhere) |
| `iata_code` (e.g. `LHR`) | airports — transfer/chauffeur points, car_hire pickup/return |
| `station_code` / `*_station_uic` | rail (train) |
| `address` (free text) | human label / fallback — **not** used to search |

**Concrete:** a `tour`/`restaurant` request should carry **both `city_code` + `country_code` AND `geo` (+ `radius_km`)** — then the city-based suppliers *and* the geo-based suppliers can both serve it. A `transfer`/`chauffeur` point must carry **`geo` or `iata_code`** (address-only can't be searched — PRS has no geocoder).

### PRS fills the gaps (fallback) — you stay in control

You don't *have* to resolve codes yourself. **Anything you send, PRS keeps; anything missing, PRS tries to complete** from data it holds:

| You send | PRS fills (only if absent) |
|---|---|
| `tour`/`restaurant` `city_name` (e.g. "London"), no `city_code` | → `city_code` (`LON`) so the city-based suppliers work |
| `transfer`/`chauffeur` point with only `address` that names an airport (e.g. "London Heathrow Airport") | → `iata_code` (`LHR`) **+ `geo`** so Daytrip can search |
| `car_hire` `pickup_location`/`return_location` as an airport name | → IATA code |

If you *do* send `city_code` / `geo` / `iata_code`, PRS never touches them. Resolution is conservative: an ambiguous name like "London" (5 airports) resolves to nothing rather than guessing — for transfers, still send `geo` or a specific airport.

Prefer to resolve codes yourself? Two helper endpoints (same data PRS uses):
- `GET /v1/places/cities?q=London` → `{ city_code, city_name, country_code }`
- `GET /v1/places/airports?q=heathrow` → `{ iata_code, name, municipality, iso_country, latitude, longitude }`

---

## Envelope — send on every request

| Field | Req | Notes |
|---|---|---|
| `service_type` | ★ | Luna code (`RST`/`GUI`/`ENT`/`ATR`/`MTC`/`FRY`) or PRS name (`transfer`/`tour`/`restaurant`/…) |
| `tour_id` | ★ | NOVA/Voyager tour id |
| `source` | ★ | `itinerary_builder` \| `intake_form` \| `direct_api` \| `client_preference_transfer` |
| `nova_service_id` | — | Luna/NOVA item id — captured and **echoed back** so you can correlate |
| `inquiry_id` | — | your cross-system id |
| `client_context` | — | `{ agent_code, agent_name, tour_name }` |
| `webhook_url` | — | where terminal events are POSTed (async mode) |

The response echoes `requested_service_type` (your original code) + `nova_service_id`.

---

## Field union (the "biggest set")

Send any that apply; PRS ignores what a given vertical doesn't use.

**Location (per place):** `geo{latitude,longitude}` · `country_code` · `city_code` · `iata_code` · `station_code` · `address`
**Timing:** `pickup_at` · `return_at` · `depart_at` · `date` · `time` · `duration_hours` · `radius_km`
**Party:** `pax{adults,children,infants,student_rate_eligible}` (PRS derives restaurant `party_size` from `pax` if absent)
**Details:** `name` · `vehicle_preference` · `luggage` · `vehicle_category` · `transmission` · `driver_age` · `driver_country` · `class` · `rail_pass` · `cabin_preference` · `vehicle{type,length_m,height_m}` · `interests[]` · `experience_type` · `price_tier` · `cuisine[]` · `language`

> **`name` on a `tour`/`restaurant`** narrows the result to that specific activity/venue: a name with a distinctive word ("Louvre", "Giverny") makes suppliers quote only matching products and abstain (`no_coverage`) if none match — so a named item is never mis-priced as an unrelated cheapest option. Omit `name` to get the single cheapest in the area.

Timestamps are ISO 8601 **with timezone offset** (`2026-07-10T20:00:00+01:00`); dates are `YYYY-MM-DD`.

---

## Required minimum per vertical (★ = must send)

| Vertical | Block | Required |
|---|---|---|
| **transfer** | `transfer` | `from`★ + `to`★ (each **geo or iata_code**), `pickup_at`★, `pax`★ — **≤ 7 (one vehicle); 8+ → `no_coverage`, send one request per vehicle** |
| **chauffeur** | `chauffeur` | `pickup`★ (**geo or iata_code**), `pickup_at`★, `duration_hours`★, `pax`★ |
| **tour** | `tour` | `country_code`★, `date`★, `pax`★, **+ `geo` and/or `city_code`** |
| **restaurant** | `restaurant` | `country_code`★, `date`★, `party_size`★, **+ `geo` and/or `city_code`** |
| **car_hire** | `car_hire` | `pickup_location`★, `return_location`★ (IATA preferred), `pickup_at`★, `return_at`★, `driver_age`★ |
| **train** | `train` | `origin_station`★, `dest_station`★ (UIC code better), `depart_at`★, `pax`★ |
| **ferry** | `ferry` | `origin_port`★, `dest_port`★, `depart_at`★, `pax`★ |
| **hotel** | `hotel`+`stay`+`rooms`+`pax` | see SERVICES_INTEGRATION / hotel guide |

---

## Minimum payload to cover EVERY supplier (recommended baseline)

The table above is the *schema* minimum (enough to validate + route). This is the **minimum that lets every supplier in the vertical actually quote** — so no supplier abstains for a missing locator. Send at least this per vertical (Luna code or PRS `service_type` both fine). Coords below are London for illustration.

**transfer** — covers Daytrip. Every endpoint needs `geo` (or `iata_code`). One vehicle seats ≤ 7; for 8+ send one request per vehicle.
```json
{ "service_type": "transfer", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "transfer": {
    "from": { "geo": { "latitude": 51.4700, "longitude": -0.4543 } },
    "to":   { "geo": { "latitude": 51.5074, "longitude": -0.1278 } },
    "pickup_at": "2026-07-10T09:00:00+01:00",
    "pax": { "adults": 6 }
  } }
```

**chauffeur** — covers Daytrip. Pickup needs `geo` (or `iata_code`).
```json
{ "service_type": "chauffeur", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "chauffeur": {
    "pickup": { "geo": { "latitude": 51.5074, "longitude": -0.1278 } },
    "pickup_at": "2026-07-10T10:00:00+01:00",
    "duration_hours": 6,
    "pax": { "adults": 4 }
  } }
```

**tour** — covers ALL four (Viator + Daytrip + Tiqets via `city_code`; Amadeus via `geo`). Send **both** locators.
```json
{ "service_type": "tour", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "item:…",
  "tour": {
    "city_code": "LON", "country_code": "GB",
    "geo": { "latitude": 51.5194, "longitude": -0.1270 }, "radius_km": 5,
    "date": "2026-07-10",
    "pax": { "adults": 6 },
    "name": "British Museum"
  } }
```

**restaurant** — covers TheFork (geo + name). Include `city_code` too for any future city-based dining supplier.
```json
{ "service_type": "restaurant", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "restaurant": {
    "geo": { "latitude": 51.5212, "longitude": -0.1550 }, "radius_km": 5,
    "city_code": "LON", "country_code": "GB",
    "date": "2026-07-10", "time": "20:00",
    "party_size": 6,
    "name": "Local Marylebone Gastropub"
  } }
```

**car_hire** — covers AutoEurope. IATA codes for pickup/return.
```json
{ "service_type": "car_hire", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "car_hire": {
    "pickup_location": "LHR", "return_location": "LHR",
    "pickup_at": "2026-07-10T09:00:00Z", "return_at": "2026-07-17T18:00:00Z",
    "driver_age": 35
  } }
```

**train** — covers Rail Europe. Station names work; UIC codes (`origin_station_uic`/`dest_station_uic`) are more reliable.
```json
{ "service_type": "train", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "train": {
    "origin_station": "Paris Gare du Nord", "dest_station": "London St Pancras",
    "depart_at": "2026-07-10T10:13:00+02:00",
    "pax": { "adults": 2 }
  } }
```

**ferry** — covers Direct Ferries (once added).
```json
{ "service_type": "ferry", "tour_id": "…", "source": "itinerary_builder", "nova_service_id": "…",
  "ferry": {
    "origin_port": "Dover", "dest_port": "Calais",
    "depart_at": "2026-07-10T08:00:00+01:00",
    "pax": { "adults": 4 }
  } }
```

> If you send these baselines and still get `no_suppliers_enabled` / `no_coverage`, it's a **supplier not switched on** (Amadeus/Tiqets/TheFork/Rail keys), not a payload problem — see the status table at the bottom.

---

## Maximal examples (fill what you have)

**transfer**
```json
{
  "service_type": "transfer", "tour_id": "…", "source": "itinerary_builder",
  "nova_service_id": "item:…",
  "transfer": {
    "from": { "geo": { "latitude": 49.0097, "longitude": 2.5479 }, "iata_code": "CDG", "address": "Paris CDG" },
    "to":   { "geo": { "latitude": 48.8443, "longitude": 2.3270 }, "city_code": "PAR", "country_code": "FR", "address": "Hôtel Le Six" },
    "pickup_at": "2026-07-10T16:30:00+02:00",
    "pax": { "adults": 6 }, "luggage": 6, "vehicle_preference": "any"
  }
}
```

**tour** (activities / museum & attraction tickets)
```json
{
  "service_type": "tour", "tour_id": "…", "source": "itinerary_builder",
  "nova_service_id": "item:…",
  "tour": {
    "geo": { "latitude": 51.5194, "longitude": -0.127 }, "radius_km": 5,
    "city_code": "LON", "country_code": "GB",
    "date": "2026-07-10", "name": "British Museum",
    "pax": { "adults": 6 }, "interests": ["history"], "language": "en"
  }
}
```

**restaurant**
```json
{
  "service_type": "restaurant", "tour_id": "…", "source": "itinerary_builder",
  "nova_service_id": "…",
  "restaurant": {
    "geo": { "latitude": 51.5212, "longitude": -0.155 }, "radius_km": 5,
    "city_code": "LON", "country_code": "GB",
    "date": "2026-07-10", "time": "20:00", "party_size": 6,
    "name": "Local Marylebone Gastropub", "experience_type": "any", "cuisine": ["british"]
  }
}
```

**chauffeur**
```json
{
  "service_type": "chauffeur", "tour_id": "…", "source": "itinerary_builder",
  "chauffeur": { "pickup": { "iata_code": "PRG", "geo": { "latitude": 50.10, "longitude": 14.26 } },
    "pickup_at": "2026-07-10T10:00:00+02:00", "duration_hours": 6, "pax": { "adults": 3 }, "vehicle_preference": "van" }
}
```

**car_hire**
```json
{
  "service_type": "car_hire", "tour_id": "…", "source": "itinerary_builder",
  "car_hire": { "pickup_location": "LHR", "return_location": "LHR",
    "pickup_at": "2026-07-10T09:00:00Z", "return_at": "2026-07-17T18:00:00Z",
    "driver_age": 35, "vehicle_category": "economy", "transmission": "automatic" }
}
```

**train**
```json
{
  "service_type": "train", "tour_id": "…", "source": "itinerary_builder",
  "train": { "origin_station": "Paris Gare du Nord", "dest_station": "London St Pancras",
    "depart_at": "2026-07-10T10:13:00+02:00", "pax": { "adults": 2 }, "class": "standard" }
}
```

**ferry**
```json
{
  "service_type": "ferry", "tour_id": "…", "source": "itinerary_builder",
  "ferry": { "origin_port": "Dover", "dest_port": "Calais", "depart_at": "2026-07-10T08:00:00+01:00",
    "pax": { "adults": 4 }, "vehicle": { "type": "car" }, "cabin_preference": "any" }
}
```

---

## Worked example — why "British Museum" returned no price

Luna sent a `tour` with `geo` but **no `city_code`**. The enabled tour suppliers (Viator, Daytrip) are **city-based** and abstained (`no_coverage`); the geo-based supplier (Amadeus) that handles museum tickets isn't keyed yet. **Fix per the rule above:** include `city_code: "LON"` + `country_code: "GB"` *alongside* `geo` — then Viator/Daytrip serve it immediately, and Amadeus serves the geo path once its key is set.

---

## Supplier status (who can actually price, today)

| Vertical | Enabled now | Pending |
|---|---|---|
| transfer / chauffeur | Daytrip (sandbox) | — |
| tour | Viator, Daytrip (need `city_code`) | Amadeus + Tiqets (need keys) — the geo/ticket path |
| restaurant | — | TheFork (partner API) |
| car_hire | AutoEurope (demo-fixtures only) | live API |
| train | — | Rail Europe creds |
| ferry | — | no supplier yet |

So even a perfect payload returns `no_suppliers_enabled` / `no_coverage` for verticals whose supplier isn't switched on — that's credential/onboarding, not a payload problem.
