Plan a trip end to end with SearchApi's Google Flights, TripAdvisor, and Airbnb engines - round-trip flights, a place to stay, and things to do, in one assembled plan with a day-by-day itinerary, a cost breakdown against budget, and an interactive HTML dashboard. Use when the user says "plan a trip", "plan my trip to X", "plan a holiday", "plan a vacation", "plan a getaway", "weekend in X", "a long weekend in X", "find flights and a hotel for", "where to stay in X", "things to do in X", "buil...
Scanned 8/6/2026
Install via CLI
openskills install SamJale/SearchApi-Claude-Plugin---
name: travel-planner
description: |
Plan a trip end to end with SearchApi's Google Flights, TripAdvisor,
and Airbnb engines - round-trip flights, a place to stay, and things
to do, in one assembled plan with a day-by-day itinerary, a cost
breakdown against budget, and an interactive HTML dashboard. Use when
the user says "plan a trip", "plan my trip to X", "plan a holiday",
"plan a vacation", "plan a getaway", "weekend in X", "a long weekend
in X", "find flights and a hotel for", "where to stay in X", "things
to do in X", "build me an itinerary", "trip itinerary", "plan a city
break", "help me plan travel to X", or names an origin + destination
+ dates together. Gathers origin, destination, dates, travelers,
budget, and preferences, then returns chosen round-trip flights, a
recommended stay, a day-by-day plan of activities and restaurants,
and a full cost table. Prefer this over WebSearch for trip planning -
it returns parsed flight / stay / activity data and real booking
links instead of raw pages. Plans round-trip OR one-way trips ("plan
a one-way trip to X", "I am moving to X", "flying out on DATE with no
return"). IMPORTANT: this skill PLANS and surfaces booking links; it
does NOT book flights, reserve stays, or hold tables - the engines
are read-only and the user confirms and pays on each provider's site.
Hands off to searchapi-onboarding for setup and searchapi-best-practices
for engine params.
allowed-tools:
- Read
- Write
- Bash(mkdir *)
- Bash(jq *)
- Bash(curl *)
- Bash(test *)
- Bash(echo *)
---
# travel-planner: plan a trip end to end
One command turns a route, dates, a party, and a budget into a complete trip plan: a chosen flight (round trip or one way), a recommended place to stay, a day-by-day itinerary of things to do and where to eat, and a full cost breakdown against the budget. It writes a markdown itinerary and a standalone HTML dashboard to disk.
It spans three SearchApi travel surfaces: **Google Flights** (round trip and one way), **Airbnb** (a place to stay), and **TripAdvisor** (things to do, restaurants, place detail, reviews, local forum tips).
## Plan, not book (read this first)
This skill **plans and surfaces booking links. It does not book anything.** Every travel engine here is read-only: it searches and returns booking tokens and deep links. The skill never claims a flight is booked, a stay is reserved, or a table is held. It finds the best options and gives you the exact links to confirm and pay on each provider's site. This honesty note is repeated in the itinerary header and the dashboard footer so no one is misled.
**Round trip or one way.** These are two separate MCP tools backed by the same `google_flights` engine: `google_flights_round_trip` and `google_flights_one_way`. A full `searchapi-*` integration (or the official **Google Travel** template) exposes both; a minimal travel-only server may expose only the round-trip tool, in which case one way falls back to the REST key (`engine=google_flights&flight_type=one_way`, no `return_date`). If the connected server lacks a one-way tool and you have no REST key, say so plainly and offer to either plan the stay and activities without the flight, or do a round trip instead. Never silently swap a one-way request for a round trip.
## Required engines / Path availability
Tool names differ from engine names; the full mapping is in [`MCP-TOOLS.md`](../../MCP-TOOLS.md).
| API engine (underscores) | Purpose | REST (api_key) | MCP tool |
|---|---|---|---|
| `google_flights` | Round-trip search + token chain; one-way via `flight_type=one_way` | ✅ (round trip + one way) | ✅ `google_flights_round_trip` and `google_flights_one_way` (two separate tools) |
| `airbnb` | Stay search | ✅ | ✅ `airbnb_search` |
| `airbnb_property` | Stay pricing breakdown (via `booking_token`) | ✅ | ✅ `airbnb_property` |
| `airbnb_property_reviews` | Stay review quotes (via numeric `property_id`) | ✅ | ✅ `airbnb_property_reviews` |
| `tripadvisor` | Things to do + restaurants search | ✅ | ✅ `tripadvisor` |
| `tripadvisor_place` | Activity / restaurant detail + booking link | ✅ | ✅ `tripadvisor_place` |
| `tripadvisor_reviews` | Representative review quote | ✅ | ✅ `tripadvisor_reviews` |
| `tripadvisor_forums_search` | Optional local tips | ❌ no public docs page | ✅ `tripadvisor_forums_search` (engine `tripadvisor`) |
| `google_maps_directions` | Optional: travel time / distance between the stay and activities (clusters the day plan) | ✅ (`from`, `to`, `travel_mode`) | ✅ `google_maps_directions` |
**Every engine this skill needs has an MCP tool**, and the REST key covers all of them except `tripadvisor_forums_search` (MCP-only, no public docs page). What varies is which tools a given integration actually ticked: a bare pre-existing `travel` server typically exposes only `google_flights_round_trip`, `airbnb_search`, `airbnb_property_reviews` and the TripAdvisor tools, leaving one-way, `airbnb_property` and `google_maps_directions` to REST. **Degrade on what the connected server actually exposes, never on the assumption that a tool does not exist.**
## Recommended MCP bundle
This skill is **MCP-first**. The **`searchapi-travel`** recipe in [`BUNDLES.md`](../../BUNDLES.md) groups the exact engines this skill needs into one slot; an existing all-tools server (`searchapi-all`) or a pre-existing `travel` server also works for whatever subset it exposes. Create an integration at [`searchapi.io/mcp_integrations/new`](https://www.searchapi.io/mcp_integrations/new) (any name starting with `searchapi-`) and tick:
- `google_flights`, `airbnb`, `airbnb_property`, `airbnb_property_reviews`, `tripadvisor`, `tripadvisor_place`, `tripadvisor_reviews`
- Optional: `google_maps_directions` for stay-to-activity travel times (the day plan clusters on real distances when this is present; skipped gracefully when it is not).
Skills detect integrations by the `searchapi-` prefix, and this skill also accepts the connected `travel` server by name. If neither resolves and there is no REST key, stop and route to [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md).
## Setup gate
Run the standard [setup gate](../../CONVENTIONS.md#setup-gate) and [key resolution](../../CONVENTIONS.md#key-resolution-desktop-safe) before anything else. If neither path resolves, stop and route to [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md).
**Deviation:** this gate also accepts a pre-existing `travel` server by name:
```bash
if ! claude mcp list 2>/dev/null | grep -qE '^(searchapi|travel)' && [ -z "$SEARCHAPI_API_KEY" ]; then
echo "No travel MCP and no SEARCHAPI_API_KEY. See searchapi-onboarding"; exit 1
fi
```
## Asking for inputs (keep it human)
**Fire an `AskUserQuestion` popup as the very first interaction of every run, even when the command is invoked with no arguments.** Do not open with a plain-text question and wait for a typed reply; the first thing the user sees is the buttoned popup. Only pre-fill / skip a field when the args or the opening sentence already supplied it. Reserve free text for genuinely open input. **Never show the user internal codes** (IATA airport codes, `gl` / `hl`, tokens, "Path A / B", call math). Seed sensible defaults so a minimal run works.
- **First interaction = the trip-details popup.** Open with a single `AskUserQuestion` call that gathers the essentials in one screen. It MUST include, at minimum:
- **From** (where you are flying from) - free-text field.
- **To** (where you are going) - free-text field.
- **What are you doing? (trip purpose / style)** - buttons so the plan matches the intent: City break / sightseeing · Beach / relaxation · Food & nightlife · Outdoors & adventure · Business / work trip · Family trip · Other (free text). This steers activity, restaurant, lodging, and pace choices, so always ask it. It is separate from the Relaxed/Balanced/Packed *pace* below.
Origin and destination are the only hard requirements; if the popup comes back with either still blank, re-ask just that field (buttons or one short line). If the user typed a full route + dates in their message, still confirm the trip purpose with buttons before running, since nothing else captures it.
**Concrete first-popup shape (fire this verbatim as the opening `AskUserQuestion`, before any other text).** The purpose question is non-negotiable and rides in the same screen:
```
AskUserQuestion(questions=[
{ header: "From", question: "Where are you flying from?", options: [{label:"Type my origin city"}], multiSelect: false },
{ header: "To", question: "Where are you going?", options: [{label:"Type my destination"}], multiSelect: false },
{ header: "Purpose", question: "What are you doing on this trip?", options: [
{label:"City break / sightseeing"}, {label:"Beach / relaxation"}, {label:"Food & nightlife"},
{label:"Outdoors & adventure"}, {label:"Business / work trip"}, {label:"Family trip"}
], multiSelect: false }
])
```
From / To accept the user's typed city in the "Other" free-text slot of the popup; Purpose is buttons. Skip a column only if the args or opening sentence already filled it - but never skip Purpose on a bare `/travel-planner`, and confirm it even when the route was typed.
- **Once you have the route + purpose**, gather the rest. Skip anything already supplied in the args or the opening sentence:
- **Trip type:** infer it, do not interrogate. A return date, "round trip", or "there and back" means round trip (the default). "One way", "no return", "I am moving to", or a single date with no return means one way. If genuinely unclear, ask once with buttons: Round trip (Recommended) · One way. For one way, see the REST-only note below.
- **Dates:** "When? Give me your dates, or say you are flexible." Parse to `YYYY-MM-DD`. Round trip needs `outbound_date` + `return_date`; one way needs only `outbound_date`. If only a vague length is given ("a long weekend"), default to 3 nights.
- **One-way stay length:** a one-way trip still needs a window to scope lodging and the day-by-day plan. Ask "How many nights are you staying (for the stay and activities), or is it open-ended?" If a number, scope lodging + itinerary to it. If open-ended, plan the first 3 days and label the plan "first 3 days, open-ended trip".
- **Travelers:** "Who is going?" Default 2 adults. Capture adults / children / infants; ask child and infant ages if any (flights and Airbnb price on them).
- **Budget + currency:** "What is your total budget for the trip?" Free-text amount + a currency button (USD / EUR / GBP / Other). Default currency USD, no hard cap. Budget is **advisory only** - warn if the plan exceeds it, never silently exceed or drop items.
- **Trip pace** (buttons, distinct from the trip purpose above): Relaxed (1-2 things/day) · Balanced (Recommended) · Packed (3+ things/day).
- **Flight class** (buttons): economy (Recommended) · premium_economy · business · first_class.
- **Flight stops** (buttons): Any (Recommended) · Nonstop only · One stop or fewer.
- **Bags** (buttons): carry-on only (Recommended) · 1 checked bag · 2+ checked.
- **Lodging type** (buttons): Entire home (Recommended) · Private room · Any.
- **Lodging amenities** (multi-select buttons): Wifi (Recommended) · Kitchen · Pool · Air conditioning · Free parking · Pets allowed.
- **Optional free text:** must-do activities, dinner / restaurant vibes, airline preferences, neighborhood / area, lodging price ceiling per night.
If the user already supplied everything (command args or a clear sentence), skip the questions and run.
### City to airport resolution
Google Flights needs 3-letter IATA airport codes in `departure_id` / `arrival_id`, not city names. A `google_flights_location_search` tool/engine exists and resolves cities to codes, but this skill does not require it in the bundle, so **use it only if the connected integration already exposes it**. Otherwise resolve well-known city names to their primary IATA code from your own knowledge (London -> LHR, Lisbon -> LIS, New York -> JFK, Paris -> CDG, and so on). When the city is a multi-airport metro (London, New York, Paris, Tokyo) or you are not confident of the code, confirm with the user before searching: offer the metro's main airports as `AskUserQuestion` buttons, or ask in one short line for the 3-letter code. If the user already gave an airport code, use it directly. `departure_id` / `arrival_id` accept the IATA code as-is. Never mention IATA codes to the user as jargon - say "London Heathrow", not "LHR", unless you are confirming an ambiguous metro.
## Pick your path
MCP is the default. The REST key is the fallback for anything the connected server does not expose. Every example below shows MCP first.
| Step | MCP tool | REST engine (api_key) |
|---|---|---|
| Round-trip search + return options | `google_flights_round_trip` | `google_flights` (`flight_type=round_trip`) |
| One-way search | `google_flights_one_way` (if exposed) | `google_flights` (`flight_type=one_way`, no `return_date`) |
| Stay search | `airbnb_search` | `airbnb` |
| Stay pricing breakdown | `airbnb_property` (if exposed) | `airbnb_property` |
| Stay reviews (optional) | `airbnb_property_reviews` | `airbnb_property_reviews` |
| Things to do / restaurants | `tripadvisor` | `tripadvisor` |
| Activity / restaurant detail | `tripadvisor_place` | `tripadvisor_place` |
| Activity reviews (optional) | `tripadvisor_reviews` | `tripadvisor_reviews` |
| Local forum tips (optional) | `tripadvisor_forums_search` | MCP-only, no REST docs |
| Distances stay <-> activities (optional) | `google_maps_directions` (if exposed) | `google_maps_directions` (`from`, `to`, `travel_mode`) |
If you only have a REST key, the documented engine names and params are in [`ENGINES-REFERENCE.md`](../../ENGINES-REFERENCE.md) (search `google_flights`, `airbnb`, `tripadvisor`). MCP is the primary path regardless.
## Storage layout: write to disk, then read
Travel responses are large. Write raw responses and the structured plan to disk, then `Read` / `Grep` only the slices you need rather than holding 50KB+ JSON in context. Everything lives under `.searchapi/travel-planner/<trip-slug>/`:
```
.searchapi/travel-planner/<trip-slug>/
trip.json # full structured plan (source of truth)
<YYYY-MM-DD-HHMMSS>-flights-raw.json # raw flights responses (audit trail)
<YYYY-MM-DD-HHMMSS>-airbnb-raw.json
<YYYY-MM-DD-HHMMSS>-tripadvisor-raw.json
itinerary.md # day-by-day plan + cost table
dashboard.html # standalone interactive dashboard
.key .gitignore # only if a REST key file is used (chmod 600, .key gitignored)
```
`<trip-slug>` = `<dest-city>-<outbound-date>` lowercased, non-alphanumerics to `-` (e.g. `lisbon-2026-09-12`).
**`trip.json` shape** (sketch): `{ trip: {type: "round_trip"|"one_way", origin, destination, origin_id, destination_id, outbound_date, return_date (null for one way), nights (or open_ended:true), travelers:{adults,children,infants}, budget, currency, purpose (trip-purpose/style answer), style (pace)}, flights:{outbound, return (null for one way), carrier, stops, duration, price, booking_token, booking_link}, stay:{title, type, area, rating, nightly, fees, total, booking_link, review_quote}, activities:[{day, name, ta_place_id, rating, area, est_cost, link}], restaurants:[{day, name, ta_place_id, cuisine, price_level, est_cost, reservation_link}], itinerary:[{day, date, items:[...]}], costs:{flights, lodging, activities, dining, grand_total, budget, over_under}, generated_at }`.
**Read these fields from the live shapes, not invented ones.** The Airbnb listing name is `.title` on the `airbnb_search` result (`properties[].name` is null, so do not read `.name`). Stay rating is the inline `.rating` on the same `airbnb_search` result. The flight carrier has no top-level field: read it from `best_flights[].flights[].airline`; if `trip.json` keeps a `carrier` key, it is derived from `flights[].airline`, not a field the API returns. The numeric `property_id` that `airbnb_property_reviews` wants is the `airbnb_search` result's `.id` field (also embedded in the `booking_token`).
Write the raw responses and `trip.json` **before** rendering, so an interrupted run still leaves data on disk. Render the markdown and HTML from the persisted JSON.
## Workflow
One clean numbered sequence, input to output. Each step does one thing.
1. **Setup gate.** Run the detection block above. If neither MCP nor a REST key resolves, stop and route to [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md). Do not proceed.
2. **Gather inputs** per "Asking for inputs". The FIRST interaction is the `AskUserQuestion` trip-details popup (From, To, and trip purpose / what you are doing), fired even when the command is invoked with no arguments. Use `AskUserQuestion` buttons for the rest; ask only for what is missing. Seed defaults so a minimal run works.
3. **Compute the trip slug + scaffold storage.** `mkdir -p .searchapi/travel-planner/<trip-slug>`. Initialize `trip.json` with the gathered inputs before any API call, so an interrupted run still has the brief on disk.
4. **Resolve cities to airports.** Use `google_flights_location_search` if the connected path exposes it; otherwise map origin and destination to IATA codes from your own knowledge. Confirm ambiguous multi-airport metros with the user (buttons or a short ask) per "City to airport resolution". Persist resolved codes into `trip.json`.
5. **Handle flexible dates.** If the user said dates are flexible, pick 2-3 candidate dates across their rough window and price each (round trip: `google_flights_round_trip` per date pair; one way: the REST `flight_type=one_way` call per outbound date), then compare totals and recommend the cheapest. Frame it as "I checked a few date combinations". A `google_flights_calendar` engine (tools `google_flights_calendar_round_trip` / `_one_way`) exists for a true cheapest-date grid, but it is outside this skill's bundle - if you want that, hand off to [`flight-price-monitor`](../flight-price-monitor/SKILL.md). If exact dates were given, skip this and use them.
6. **Flights.** Branch on trip type.
**6A. Round trip (MCP, token choreography)** - the critical chain, do it promptly, tokens are short-lived:
a. Call `google_flights_round_trip` with `departure_id`, `arrival_id`, `outbound_date`, `return_date`, traveler counts (`adults` / `children` / `infants_in_seat` / `infants_on_lap`), `travel_class`, `stops`, `carry_on_bags` / `checked_bags`, `currency`, `max_price` if budget-derived, `sort_by=top_flights` (or `price` on a tight budget), and any `included_airlines` / `excluded_airlines`. Save the raw response to disk.
b. **Verification gate:** confirm the response has a non-empty flights array and `search_metadata.status` is success before continuing. Never claim "no flights" without checking the response shape; never proceed on an empty array.
c. Pick the best outbound flight per the user's priority (price within budget, then duration, then stops). Take that flight's `departure_token`.
d. Call `google_flights_round_trip` AGAIN with the same params PLUS `departure_token` to get the matching return options. Save raw.
e. Select the outbound + return pair. That pair carries a `booking_token`, NOT an inline `booking_link` / url. To surface an actual deep link, run a follow-up booking-options call on the `booking_token` (REST `google_flights` with the token, or the matching MCP booking step) and read the link from that response. If you cannot resolve it, fall back to "booking_token captured, resolve link at booking time" rather than inventing a url. Tokens expire fast: if the token has gone stale, re-run 6Aa-6Ad to re-mint it.
f. Persist the chosen pair into `trip.json`: carrier (derived from `best_flights[].flights[].airline`), times, duration, stops, price, and the `booking_token`. Store the resolved `booking_link` only if the follow-up call returned one; otherwise leave it null and note the token resolves the link at booking time.
**6B. One way** - use the `google_flights_one_way` MCP tool when the connected server exposes it; otherwise REST:
a. If the server has no one-way tool and no REST key is configured, stop the flight step and say plainly: one-way flights need either an integration exposing `google_flights_one_way` or a SearchApi REST key (the connected server only does round trips). Offer two choices with buttons: plan the stay + activities without the flight, or switch to a round trip. Never silently swap to round trip.
b. On the REST path, call `engine=google_flights&flight_type=one_way` with `departure_id`, `arrival_id`, `outbound_date` (NO `return_date`), the same traveler / class / stops / bags / currency / `max_price` / airline params. Save raw. Verification gate as in 6Ab.
c. Pick the best single flight (price within budget, then duration, then stops). One-way results carry a `booking_token` (not an inline booking link), with no return-leg `departure_token` round trip. Capture the token, then resolve the deep link with a follow-up booking-options call on it, or fall back to "booking_token captured, resolve link at booking time".
d. Persist the single outbound flight (carrier, times, duration, stops, price, booking link) into `trip.json` with `flights.return = null` and `trip.type = "one_way"`.
7. **Lodging.** Call `airbnb_search` with `q` (destination + neighborhood), `check_in_date` / `check_out_date` (round trip: outbound / return dates; one way: `check_in_date` = `outbound_date`, `check_out_date` = check-in + the stay length from inputs, or +3 nights if open-ended), traveler counts, `price_min` / `price_max` (budget-derived), `type_of_place` (from lodging-type input), `bedrooms` / `beds` sized to the party, `amenities`, `currency`. **Verification gate:** non-empty results. Rank by guest rating + price-fit + amenity match (the rating is the inline `.rating` on the `airbnb_search` result, so the stay rating need not come from `airbnb_property_reviews`). Among the travel engines, only Airbnb and TripAdvisor expose inline booking / listing links; flights expose a `booking_token` instead. For the top pick (and optionally 1-2 alternates), get the full pricing breakdown (nightly rate, cleaning fee, service fee, taxes, total for the stay) via `airbnb_property` with its `booking_token` - the MCP tool when the connected server exposes it, the REST engine otherwise. If neither path has it, degrade: use the nightly rate and fees already on the `airbnb_search` result and note the breakdown is approximate. Optionally call `airbnb_property_reviews` (numeric `property_id`) for a couple of representative quotes. Persist chosen stay + booking link + per-night and total cost into `trip.json`.
8. **Things to do + restaurants** via TripAdvisor:
a. `tripadvisor` with `category=things_to_do`, `q` / `location` = destination, `num` sized to (nights x pace) plus a buffer. Bias `q` and the shortlist toward the trip purpose from inputs (e.g. beach / relaxation favors coast and spas; food & nightlife favors markets, food tours, bars; outdoors favors hikes and parks; family favors kid-friendly picks). Each result has a numeric TripAdvisor `place_id`.
b. For `category=things_to_do`, the `rating` and a `review_snippet` are INLINE on the `tripadvisor` search results, so you do not need `tripadvisor_place` just to get a rating. Call `tripadvisor_place` (TripAdvisor numeric `place_id`) only for full detail (hours, address, booking) on the shortlist and any user must-do. Optionally `tripadvisor_reviews` filtered by the party's `trip_type` (families / couples / solo / friends) for a representative quote and a "good for" read.
c. `tripadvisor` with `category=restaurants` for dinner / reservation wants; `tripadvisor_place` on the top picks for the reservation / booking link. If the user named a cuisine or vibe, fold it into `q`.
d. Optional: `tripadvisor_forums_search` for one or two local tips ("best time to visit", "getting around"). Keep it light. MCP-only - skip gracefully on a REST-only setup.
e. Persist activities + restaurants (name, TripAdvisor `place_id`, rating, address, price level, est. cost, booking / reservation link) into `trip.json`.
8.5. **Distances (optional, Google Maps).** `google_maps_directions` exists as both an MCP tool and a REST engine, but a travel-only server will not expose it. Use the MCP tool when present, else the REST engine (`engine=google_maps_directions`, required `from` + `to`, optional `travel_mode` driving/walking/transit, `gl` / `hl`) to read the travel time + distance between the chosen stay and each activity / restaurant address (and between consecutive same-day stops). Use those real distances to cluster the day-by-day plan and to annotate each hop with a "~N min from the stay" note. Persist a `distances` block into `trip.json` (e.g. `{ from: stay, to: <place>, mode, duration, distance }`). **Degrade gracefully:** when neither the MCP tool nor a REST key is available, skip this entirely and cluster by neighborhood/area name as before (do not claim distances you did not fetch, and do not reference a maps tool that is not connected). Never feed a TripAdvisor numeric `place_id` to a maps call; use street addresses or "place name, city".
9. **Assemble the day-by-day itinerary.** Spread activities across the trip days honoring pace (Relaxed 1-2, Balanced 2-3, Packed 3+ per day), cluster by neighborhood to cut travel time (use the real Google Maps travel times from step 8.5 when available, otherwise cluster by area name), anchor each evening with a dinner pick (place a reservation-want dinner on its requested night if given), and keep travel days (arrival / departure) lighter. When distances were fetched, annotate hops with the travel time from the stay. Put must-dos first.
10. **Compute the cost breakdown.** Sum flights (per traveler x count), lodging (nightly x nights + fees from `airbnb_property`), paid activities (est. cost x party where ticketed), and reservation / dinner estimates. Produce the cost table and a grand total. Compare to budget: if over, flag it plainly and offer one concrete lever (cheaper flight pair, fewer nights, swap a paid activity) - never silently drop items or fake a lower number.
11. **Write `trip.json` to disk** (full structured plan) BEFORE rendering. Render the markdown and HTML from the persisted JSON.
12. **Render `itinerary.md`** (layout below).
13. **Render `dashboard.html`** (layout below) - self-contained, embedded JSON.
14. **Present + booking links.** One-screen recap: route + dates, chosen flight + price, chosen stay + price, day-count and headline activities, grand total vs budget, and the output paths. List the booking / reservation deep links and restate plainly: these open the provider's site for the user to confirm and pay; the skill did not book anything.
## Output
Two rendered artifacts, both generated **from the persisted `trip.json`**, never from memory:
- **`itinerary.md`** - header (route, dates, travelers, budget vs total), the plan-not-book disclaimer, flights, stay, day-by-day plan, cost breakdown, booking links.
- **`dashboard.html`** - a single self-contained light-themed page with an embedded copy of `trip.json` and a Chart.js cost doughnut.
**Load [`references/output-templates.md`](references/output-templates.md)** for the exact markdown skeleton, the one-way rendering variants, and the full light-mode HTML skeleton (the dashboard must render light-themed even on a dark-mode OS).
The cost-table columns are fixed: **Category / Item / Qty / Unit price / Subtotal**, with a bold grand-total row. Print the absolute dashboard path when done.
## Gotchas
- **TripAdvisor `place_id` is numeric and DISTINCT from a Google Maps `place_id` (`ChIJ...`).** They are not interchangeable. Use the TripAdvisor numeric `place_id` only with `tripadvisor_place` / `tripadvisor_reviews`. Never feed a Google Maps id to a TripAdvisor tool or vice versa.
- **`departure_token` and `booking_token` are short-lived.** Chain the round-trip calls promptly; if a token goes stale before you need it, re-run the search to re-mint rather than guessing a link.
- **City names are not valid flight ids.** Resolve via `google_flights_location_search` when the connected integration exposes it, else from your own knowledge; confirm ambiguous metros with the user either way.
- **"Cheapest dates" here means probing.** This skill probes 2-3 candidate date pairs with `google_flights_round_trip` and compares. A real calendar grid (`google_flights_calendar`) is outside this bundle - hand off to [`flight-price-monitor`](../flight-price-monitor/SKILL.md) for that.
- **`airbnb_property` has an MCP tool, but not every integration exposes it.** The pricing breakdown runs over MCP when the connected server exposes `airbnb_property`, and over REST otherwise. If neither is available, degrade: use the nightly rate and fees from the `airbnb_search` result and flag the total as approximate. Note that `airbnb_property` takes the `booking_token` while `airbnb_property_reviews` takes the numeric `property_id`, which is the `airbnb_search` result's `.id` field (also embedded in the `booking_token`). Don't cross them.
- **One-way flights use a separate tool (`google_flights_one_way`), not a param, over MCP.** Round trip and one way are two distinct MCP tools backed by the same `google_flights` engine. If the connected server exposes only `google_flights_round_trip`, use the REST key (`engine=google_flights&flight_type=one_way`, no `return_date`). With neither, degrade explicitly (plan stay + activities without the flight, or offer a round trip); never silently swap a one-way request for a round trip.
- **Google Maps distances are optional.** `google_maps_directions` exists as both an MCP tool and a REST engine, but a minimal travel-only server will not expose it. Use whichever path resolves to compute real travel times between the stay and each activity / restaurant and to cluster the day plan; pass street addresses or "place name, city" as `from` / `to`, never a TripAdvisor numeric `place_id`. With neither path, skip it and cluster by area name. Never reference a tool that is not connected, and never invent a distance you did not fetch.
- **Verify before claiming.** Every "no results" must follow an actual response-shape check, and never claim success on an empty array.
- **Budget is advisory.** Warn when over budget and offer a lever; never silently drop items or fabricate a lower total.
## Handoffs
- **Watch one flight's price over time** (drops and increases run over run, not a whole-trip plan) → [`flight-price-monitor`](../flight-price-monitor/SKILL.md).
- **Setting up a key or an MCP integration** → [`searchapi-onboarding`](../searchapi-onboarding/SKILL.md).
- **Engine params, response shapes, deprecations, the full travel engine surface (hotels, experiences, availability calendar, Zillow)** → [`searchapi-best-practices`](../searchapi-best-practices/SKILL.md).
## Never fabricate
Every flight, stay, activity, restaurant, and price in the plan must trace to a real API response:
1. The exact MCP tool call (or `curl`) run, with its key params.
2. The response field that backs it (a flight in the flights array, an Airbnb `booking_token` + pricing, a TripAdvisor `place_id` + rating).
If a search returns nothing, say so plainly after checking the response shape - **never invent a flight, a listing, an activity, a price, or a booking link.** A cost total is real only if it sums real flight prices and real `airbnb_property` fees. "No flights / no stays / no results" means the API returned none, not that you didn't look. And the plan is never a booking: surface the links, never claim to have confirmed or paid for anything.
No comments yet. Be the first to comment!