{"openapi":"3.1.0","info":{"title":"Finding Horizons API","version":"1.0.0","description":"Generated from the live action registry. See /api/v1 for the overview and /api/v1/docs for a readable guide."},"servers":[{"url":"https://findinghorizons.ca/api/v1"}],"components":{"securitySchemes":{"oauth":{"type":"oauth2","description":"OAuth 2.1 authorization code with PKCE (S256). Dynamic client registration: https://findinghorizons.ca/api/mcp/register","flows":{"authorizationCode":{"authorizationUrl":"https://findinghorizons.ca/api/mcp/authorize","tokenUrl":"https://findinghorizons.ca/api/mcp/oauth/token","refreshUrl":"https://findinghorizons.ca/api/mcp/oauth/token","scopes":{"reference:read":"Look up visa rules and local intelligence","places:read":"Read the places you have saved","places:write":"Record check-ins at your saved places, report how busy a place usually is (shared with other travellers as an estimate, from a named source), and send translation reviews","trips:read":"Read your trips and their readiness","trips:write":"Add or remove your saved places on a trip","itinerary:read":"Read your day-by-day plans (reading never changes them)","itinerary:write":"Add stops and shift days in your plan","bookings:read":"Read your stored bookings","budget:read":"Read your trip budget","budget:write":"Add lines to your trip budget","live:read":"Read live-trip status and forecasts","gameplay:read":"Read your progression and daily challenges","gameplay:write":"Accept a daily challenge as a detour","ai:replan":"Run the app's AI replanner — SPENDS THE APP'S AI BUDGET and your daily cap","ai:import":"Run the app's AI extraction on a link — SPENDS THE APP'S AI BUDGET and your daily cap","trips:manage":"Create trips and change their dates, places, pace and name; turn a guide or a road-trip plan into your own private plan; undo a change","collections:read":"Read your collections","collections:write":"Create and rename collections, and add or remove saved places in them (a place is never deleted)","guides:read":"Find published travel guides","roadtrips:read":"Work out road-trip drives, days and overnights (a preview — nothing is saved)","tasks:read":"Read a task you started in another app and what changed since","tasks:write":"Keep a task's intent, decisions and open questions, so Horizon can continue it","events:read":"Find events on record for a place and dates","events:write":"Save events to your places (saving is not buying a ticket)","chapters:read":"Read your Next Chapter places","chapters:write":"Save places to your Next Chapter","journal:read":"Read your own journal entries on your trips","journal:write":"Add private notes to your trip journal, in your words","preferences:read":"Read the travel preferences you set in Explorer DNA","reminders:write":"Set reminders that arrive on your phone at the time you ask (shown on your lock screen; quiet hours delay them)"}}}}},"schemas":{"Error":{"type":"object","properties":{"ok":{"const":false},"api":{"type":"string"},"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}},"paths":{"/capabilities":{"get":{"operationId":"capabilities","summary":"What this token can do","security":[{"oauth":[]}],"responses":{"200":{"description":"Actions, scopes, limits, write rules"},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/operations/{id}":{"get":{"operationId":"getOperation","summary":"Inspect a write (by id, or id = \"key\" with ?idempotencyKey=)","security":[{"oauth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"idempotencyKey","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The operation"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/events":{"get":{"operationId":"events","summary":"Results of your operations and changes on followed trips","security":[{"oauth":["trips:read"]}],"parameters":[{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Events (dedupe on id) and the next cursor"}}}},"/connection":{"get":{"operationId":"getConnection","summary":"This connection: app, scopes, last used","security":[{"oauth":[]}],"responses":{"200":{"description":"The connection"}}},"delete":{"operationId":"disconnect","summary":"Disconnect this connection now","security":[{"oauth":[]}],"responses":{"200":{"description":"Disconnected"}}}},"/actions/search_visa_rules":{"post":{"operationId":"search_visa_rules","summary":"Visa requirement for a passport → destination pair from Finding Horizons' scraped visa database (grounded in the live Wi","description":"Visa requirement for a passport → destination pair from Finding Horizons' scraped visa database (grounded in the live Wikipedia visa-requirements tables, refreshed on a schedule). Returns the requirement category, visa-free days, and any notes. No AI involved — this is the stored ground truth.","tags":["reference"],"security":[{"oauth":["reference:read"]}],"x-scope":"reference:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"passport":{"type":"string","description":"Passport country — ISO-2 code or name, e.g. \"CA\" or \"Canada\""},"destination":{"type":"string","description":"Destination country — ISO-2 code or name, e.g. \"TH\" or \"Thailand\""},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["passport","destination"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_local_intel":{"post":{"operationId":"get_local_intel","summary":"Local intelligence for a place from Finding Horizons: the hand-verified safety essentials (emergency number, tap water, ","description":"Local intelligence for a place from Finding Horizons: the hand-verified safety essentials (emergency number, tap water, currency, plugs, tipping, getting around, heads-up) PLUS the full pre-synthesized intelligence brief when cached — food, hidden gems, neighborhoods, money & fees, culture, phrases, scams, seasonal, risks, accessibility, family/pet/nomad notes and more. Pass city for a city-level brief; falls back to country-level. Reads the cache only — a miss returns the essentials without a brief and never generates one.","tags":["reference"],"security":[{"oauth":["reference:read"]}],"x-scope":"reference:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string","description":"Country — ISO-2 code or name"},"city":{"type":"string","description":"Optional city"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["country"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_points":{"post":{"operationId":"get_my_points","summary":"The authenticated user's saved plot points (places extracted from shared videos or saved in-app): name, city, country, c","description":"The authenticated user's saved plot points (places extracted from shared videos or saved in-app): name, city, country, coordinates, category, source link.","tags":["places"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/search_my_places":{"post":{"operationId":"search_my_places","summary":"Search the user's OWN saved places by free text, city, country, category or must-do flag","description":"Search the user's OWN saved places by free text, city, country, category or must-do flag. Deterministic substring matching over the stored rows — no model, no ranking model, no outside provider. Returns an explicit empty result (with the filters it applied) when nothing matches.","tags":["places"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Free text matched against name, description, address and notes"},"city":{"type":"string"},"country":{"type":"string","description":"Country name as stored (reverse-geocoded)"},"category":{"type":"string","description":"restaurant | cafe | attraction | nature | nightlife | experience | shop | other"},"mustDoOnly":{"type":"boolean","description":"Only places flagged must-do"},"limit":{"type":"number","description":"Max rows, 1–200 (default 50)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_place_details":{"post":{"operationId":"get_place_details","summary":"Everything stored about ONE of the user's saved places: the full row, its extraction provenance (which shared link it ca","description":"Everything stored about ONE of the user's saved places: the full row, its extraction provenance (which shared link it came from), and its check-in history. Fields with no stored value come back null — nothing is inferred or filled in.","tags":["places"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pointId":{"type":"string","description":"Point id from get_my_points / search_my_places"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["pointId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/check_in_place":{"post":{"operationId":"check_in_place","summary":"WRITE: record that the traveller actually stood at one of their OWN saved places","description":"WRITE: record that the traveller actually stood at one of their OWN saved places. Writes the point_visits ledger and recomputes XP / rank / season / badges from it. Same place, same day is a no-op (recorded:false), never a second award. Requires `date` (YYYY-MM-DD) — the server will not guess the traveller's calendar day from its own clock.","tags":["places"],"security":[{"oauth":["places:write"]}],"x-scope":"places:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pointId":{"type":"string","description":"Point id from get_my_points / search_my_places"},"date":{"type":"string","description":"The traveller's calendar date, YYYY-MM-DD"},"notes":{"type":"string"},"rating":{"type":"number","description":"1–5"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["pointId","date"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_place_notes":{"post":{"operationId":"get_place_notes","summary":"Tips and notes travellers wrote about a place — the user's own notes, friends' friends-only notes and community tips — w","description":"Tips and notes travellers wrote about a place — the user's own notes, friends' friends-only notes and community tips — with each author's @username, when it was posted, an edited mark, the tip type and any time window. Pass pointId (one of the user's saved places) or placeId (a Google place id). A tip is someone's observation, not verified fact.","tags":["places"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pointId":{"type":"string","description":"One of the user's saved places (from get_my_points)."},"placeId":{"type":"string","description":"A Google place id."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/plot_places":{"post":{"operationId":"plot_places","summary":"WRITE: put places YOU suggested in this conversation on the traveller's map, when they ask you to plot or save them (\"pl","description":"WRITE: put places YOU suggested in this conversation on the traveller's map, when they ask you to plot or save them (\"plot those\", \"add them to my map\"). Give each place's name plus its city and country. A place the Finding Horizons community has already verified is saved at once, free, with your name kept as its source. A place nobody has verified yet is returned in `needsImport`: send each through import_from_url with text \"<name>, <city>, <country>\" — never invent coordinates. Already-saved places come back in `alreadySaved`. To add the plotted places to a trip afterwards, use update_trip_stops with their ids.","tags":["places"],"security":[{"oauth":["places:write"]}],"x-scope":"places:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"places":{"type":"array","description":"Up to 15 places","items":{"type":"object","properties":{"name":{"type":"string"},"city":{"type":"string"},"country":{"type":"string"},"why":{"type":"string","description":"One line on why you suggested it — saved as the place's note"},"imageUrl":{"type":"string","description":"Optional https link to a picture of the place (copied into the app; jpeg/png/webp/gif, ≤3 MB)"},"imageBase64":{"type":"string","description":"Optional picture as base64 (e.g. one you generated), ≤3 MB"},"imageIsGenerated":{"type":"boolean","description":"true when the picture is AI-generated rather than a photo of the place — the app labels it"}},"required":["name"]}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["places"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_trips":{"post":{"operationId":"get_my_trips","summary":"The user's trips: id, title, cities, countries, dates, pace (strict/casual/fun), travelers, budget","description":"The user's trips: id, title, cities, countries, dates, pace (strict/casual/fun), travelers, budget. Use the id with build_trip_plan.","tags":["trips"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/update_trip_stops":{"post":{"operationId":"update_trip_stops","summary":"WRITE: add or remove the user's OWN saved points on a trip (by point id from get_my_points)","description":"WRITE: add or remove the user's OWN saved points on a trip (by point id from get_my_points). The day plan rebuilds from the updated list — this is how \"switch this location for that one\" happens.","tags":["trips"],"security":[{"oauth":["trips:write"]}],"x-scope":"trips:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"addPointIds":{"type":"array","items":{"type":"string"}},"removePointIds":{"type":"array","items":{"type":"string"}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_trip_health":{"post":{"operationId":"get_trip_health","summary":"The trip's readiness signals computed from PERSISTED rows: how many days are planned vs empty, booking coverage, budget ","description":"The trip's readiness signals computed from PERSISTED rows: how many days are planned vs empty, booking coverage, budget coverage and unplaced candidates. Every counter is null rather than zero when the underlying fact is unknown — a trip whose plan has never been built reports \"no days\", not \"0 planned days\".","tags":["trips"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/build_trip_plan":{"post":{"operationId":"build_trip_plan","summary":"Build the full day-by-day plan for one of the user's trips FROM THEIR OWN saved points: days honoring the trip's pace, t","description":"Build the full day-by-day plan for one of the user's trips FROM THEIR OWN saved points: days honoring the trip's pace, transport blocks between city hops (booked flights/trains with airport-survival lead-ins, or mode recommendations weighted by budget), and every ≥4-hour gap with filler suggestions from live events and community points. This runs the app's own deterministic planner — no server-side AI; you (the connected assistant) do the reasoning on top. It is a READ: it shows the plan as it stands and changes nothing (it never adds saves to the trip or saves the plan).","tags":["itinerary"],"security":[{"oauth":["itinerary:read"]}],"x-scope":"itinerary:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/suggest_gap_fillers":{"post":{"operationId":"suggest_gap_fillers","summary":"Just the ≥4-hour gaps in a trip's current plan, each with filler suggestions from live events on that date and community","description":"Just the ≥4-hour gaps in a trip's current plan, each with filler suggestions from live events on that date and community points near that day's route. Use apply_trip_filler to add one.","tags":["itinerary"],"security":[{"oauth":["itinerary:read"]}],"x-scope":"itinerary:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/apply_trip_filler":{"post":{"operationId":"apply_trip_filler","summary":"WRITE: add a stop to the user's trip plan (an accepted detour — it appears in the day plan on the next build)","description":"WRITE: add a stop to the user's trip plan (an accepted detour — it appears in the day plan on the next build). Use for gap fillers, swaps the user asked for, or detours. Only touches the user's own trip.","tags":["itinerary"],"security":[{"oauth":["itinerary:write"]}],"x-scope":"itinerary:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"title":{"type":"string","description":"Stop name"},"description":{"type":"string"},"address":{"type":"string"},"city":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"category":{"type":"string","description":"restaurant | cafe | attraction | nature | nightlife | experience | shop | other"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId","title"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/shift_trip_day":{"post":{"operationId":"shift_trip_day","summary":"WRITE: push one plan day's saved-point stops to the end of the trip, so the rebuilt plan spreads them over later days — ","description":"WRITE: push one plan day's saved-point stops to the end of the trip, so the rebuilt plan spreads them over later days — the move for a rained-out morning or a disrupted travel day. Bookings and detours stay put.","tags":["itinerary"],"security":[{"oauth":["itinerary:write"]}],"x-scope":"itinerary:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"date":{"type":"string","description":"The plan day's date, YYYY-MM-DD (from build_trip_plan)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId","date"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_bookings":{"post":{"operationId":"get_my_bookings","summary":"The traveller's stored bookings — flights, trains, accommodation, tickets — optionally filtered to one trip","description":"The traveller's stored bookings — flights, trains, accommodation, tickets — optionally filtered to one trip. Each row carries what the confirmation actually said: provider, confirmation code, start/end instants with their timezone, cost and currency. A booking with no stored time reports null rather than a guessed one, because the plan treats booking times as immovable anchors. Read-only: bookings are parsed from real confirmations, never asserted by an assistant.","tags":["bookings"],"security":[{"oauth":["bookings:read"]}],"x-scope":"bookings:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Optional — restrict to one trip (id from get_my_trips)"},"type":{"type":"string","description":"Optional — flight | train | bus | hotel | car | activity | other, as stored"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_trip_budget":{"post":{"operationId":"get_trip_budget","summary":"The deliberate budget for one trip: total, per-category envelopes with spend attributed, every line item (seeded rows la","description":"The deliberate budget for one trip: total, per-category envelopes with spend attributed, every line item (seeded rows labelled as derived), planned vs actual totals converted to the budget currency, and the per-day figure. Amounts in a currency with no FX rate are EXCLUDED and listed — `remaining` is a ceiling, not a fact, whenever that happens. Pass `date` (YYYY-MM-DD) to get the per-day figure; without it the figure is absent, never guessed from the server clock. A trip with no budget returns available:false, not zeroes.","tags":["budget"],"security":[{"oauth":["budget:read"]}],"x-scope":"budget:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"date":{"type":"string","description":"The traveller's calendar date, YYYY-MM-DD — required for the per-day figure"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/add_budget_expense":{"post":{"operationId":"add_budget_expense","summary":"WRITE: add one line to the trip budget the traveller typed themselves (source 'user' — never deleted by a budget reset, ","description":"WRITE: add one line to the trip budget the traveller typed themselves (source 'user' — never deleted by a budget reset, never overwritten by seeding). Give an estimated amount, an actual amount, or both. Category is one of: flights | accommodation | food | transport | activities | shopping | other. Currency is a 3-letter code; if it has no FX rate the line still stores honestly and is reported as excluded from converted totals.","tags":["budget"],"security":[{"oauth":["budget:write"]}],"x-scope":"budget:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"label":{"type":"string","description":"What the money was/is for"},"category":{"type":"string","description":"flights | accommodation | food | transport | activities | shopping | other"},"estimated":{"type":"number","description":"Planned amount"},"actual":{"type":"number","description":"Amount actually spent"},"currency":{"type":"string","description":"3-letter code, e.g. JPY. Defaults to the budget currency."},"notes":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId","label"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_live_day":{"post":{"operationId":"get_live_day","summary":"Today on this trip as the live engine sees it: which stop is running now, what is next, leave-by times, detected disrupt","description":"Today on this trip as the live engine sees it: which stop is running now, what is next, leave-by times, detected disruptions and the actions legally available on each item. Requires `nowMs` (epoch milliseconds from YOUR clock) — the server will not substitute its own, because it is in the wrong place. Optional `lat`/`lng` sharpen leave-by. Pure computation: no model, no paid provider, free to poll.","tags":["live"],"security":[{"oauth":["live:read"]}],"x-scope":"live:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"nowMs":{"type":"number","description":"Current instant, epoch milliseconds"},"lat":{"type":"number","description":"Optional current latitude"},"lng":{"type":"number","description":"Optional current longitude"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId","nowMs"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_forecast":{"post":{"operationId":"get_forecast","summary":"Daily forecast for a coordinate over a date range, from Open-Meteo behind the app's server cache","description":"Daily forecast for a coordinate over a date range, from Open-Meteo behind the app's server cache. HONESTLY SPARSE: a date beyond the ~16-day horizon simply has no entry (never climatology dressed as a forecast); an unusable position returns no days at all; and if the provider is down you get whatever FRESH cache exists plus `unavailable: true` — a stale entry is never served as fresh.","tags":["live"],"security":[{"oauth":["live:read"]}],"x-scope":"live:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"},"start":{"type":"string","description":"First date, YYYY-MM-DD"},"end":{"type":"string","description":"Last date, YYYY-MM-DD (defaults to start)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["latitude","longitude","start"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/replan_with_intent":{"post":{"operationId":"replan_with_intent","summary":"Recalculate the REMAINING live day for one of the traveller's eight stated intents (tired, raining, woke_late, broke_tod","description":"Recalculate the REMAINING live day for one of the traveller's eight stated intents (tired, raining, woke_late, broke_today, something_crazy, slower_afternoon, missed_transport, no_more_museums). Deterministic — the app's own rules, no AI, no spend. Returns the plan delta with per-op reasons; it writes nothing. missed_transport requires delayMinutes.","tags":["live"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"intent":{"type":"string","enum":["tired","raining","woke_late","broke_today","something_crazy","slower_afternoon","missed_transport","no_more_museums"],"description":"One of the eight intents."},"nowMs":{"type":"number","description":"Epoch ms of the traveller's NOW."},"delayMinutes":{"type":"number","description":"Required for missed_transport."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId","intent","nowMs"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/replan_from_text":{"post":{"operationId":"replan_from_text","summary":"Map a traveller's free-text request (\"we're exhausted\", \"il pleut\", \"電車を逃した\") onto ONE of the eight replan intents and r","description":"Map a traveller's free-text request (\"we're exhausted\", \"il pleut\", \"電車を逃した\") onto ONE of the eight replan intents and return the resulting plan delta for the remaining live day. Deterministic keyword mapping across ten languages — no AI, no spend, writes nothing. If it returns matched=false (didn't understand, or heard TWO intents at once), choose the intent yourself and call replan_with_intent. missed_transport still needs delayMinutes.","tags":["live"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"text":{"type":"string","description":"The traveller's words, any of the ten app languages."},"nowMs":{"type":"number","description":"Epoch ms of the traveller's NOW."},"delayMinutes":{"type":"number","description":"Used when the text maps to missed_transport."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId","text","nowMs"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/trip_stress_test":{"post":{"operationId":"trip_stress_test","summary":"Run the plan validators over the WHOLE trip and report by bucket: risky connections, overpacked days, reservation confli","description":"Run the plan validators over the WHOLE trip and report by bucket: risky connections, overpacked days, reservation conflicts, closures, backtracking, budget, weather, data gaps. Read-only; each alert carries its own resolutions.","tags":["live"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/trip_split_mode":{"post":{"operationId":"trip_split_mode","summary":"Plan a group SPLIT for the live day: given who goes in which subgroup (and optionally where each subgroup is), returns p","description":"Plan a group SPLIT for the live day: given who goes in which subgroup (and optionally where each subgroup is), returns per-subgroup allowed/blocked saved places (each subgroup constrained only by its OWN members' stated no-gos from trip_member_prefs), the reconvene anchor (the day's next locked/protected stop), and a leave-by per subgroup with a 15-min margin. Travel is a walking estimate from straight-line distance unless you pass travelMinutesToAnchor. Read-only; writes nothing.","tags":["live"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"nowMs":{"type":"number","description":"Epoch ms of the travellers' NOW."},"subgroups":{"type":"array","description":"Two or more subgroups covering the people splitting.","items":{"type":"object","properties":{"name":{"type":"string"},"memberIds":{"type":"array","items":{"type":"string"},"description":"User ids (trip members)."},"lat":{"type":"number"},"lng":{"type":"number"},"travelMinutesToAnchor":{"type":"number","description":"Measured minutes to the anchor, if you have them."}},"required":["name","memberIds"]}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId","nowMs","subgroups"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_progress":{"post":{"operationId":"get_my_progress","summary":"The traveller's progression, recomputed from real activity: XP, level, rank and next rank, the counts behind them (place","description":"The traveller's progression, recomputed from real activity: XP, level, rank and next rank, the counts behind them (places saved, places actually visited, cities, countries, collections, missions), every badge with its real progress toward its threshold, and the current season. Nothing here is a participation number — each figure traces to something that happened.","tags":["gameplay"],"security":[{"oauth":["gameplay:read"]}],"x-scope":"gameplay:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_daily_challenges":{"post":{"operationId":"get_daily_challenges","summary":"Today's challenges for a trip — at most three, deterministic for (user, trip, date), drawn from the traveller's own save","description":"Today's challenges for a trip — at most three, deterministic for (user, trip, date), drawn from the traveller's own saved places that fit a ≥4-hour gap in the day and the day's forecast. Requires `date` (YYYY-MM-DD): the server does not guess the traveller's calendar day. Returns an explicit empty list with a reason when the trip has challenges switched off, or when nothing qualifies. Zero model calls, zero paid providers.","tags":["gameplay"],"security":[{"oauth":["gameplay:read"]}],"x-scope":"gameplay:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"date":{"type":"string","description":"The traveller's calendar date, YYYY-MM-DD"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId","date"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/accept_challenge":{"post":{"operationId":"accept_challenge","summary":"WRITE: turn one of today's challenges into an accepted detour on the trip route","description":"WRITE: turn one of today's challenges into an accepted detour on the trip route. Double-gated exactly as in the app: the trip's allow_detours switch AND the traveller's own detour preference must both be on, otherwise it is refused and the blocking gate is named. The challenge is re-derived server-side for the date and must be on that day's list.","tags":["gameplay"],"security":[{"oauth":["gameplay:write"]}],"x-scope":"gameplay:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"challengeId":{"type":"string","description":"id from get_daily_challenges"},"date":{"type":"string","description":"The same calendar date the challenge was listed for, YYYY-MM-DD"},"detourPreferenceEnabled":{"type":"boolean","description":"The traveller's own detour preference. Omit and it is treated as OFF."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId","challengeId","date"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/replan_day":{"post":{"operationId":"replan_day","summary":"Ask the app's own planner to PROPOSE a reshuffle of one plan day (or the whole trip, if no date is given)","description":"Ask the app's own planner to PROPOSE a reshuffle of one plan day (or the whole trip, if no date is given). Returns a validated change-set with per-op reasons and an impact block, or an honest refusal — it writes nothing; apply the ops in the app. This is one of only two MCP tools that spends the app owner's AI budget rather than yours, so it needs the `ai:replan` scope, it counts against your daily AI cap, and it is rate-limited far more tightly than the deterministic tools. Every proposal is re-checked by the deterministic engine; anything that fails is dropped, never shown.","tags":["ai"],"security":[{"oauth":["ai:replan"]}],"x-scope":"ai:replan","x-writes":false,"x-uses-app-ai":true,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string","description":"Trip id from get_my_trips"},"date":{"type":"string","description":"The plan day to focus on, YYYY-MM-DD. Omit to consider the whole trip."},"goal":{"type":"string","description":"What the traveller wants (\"my Osaka day is too much\")"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/import_from_url":{"post":{"operationId":"import_from_url","summary":"Run the app's full extraction pipeline over a shared link (TikTok, Instagram, Reels, YouTube, a blog, a maps link) and s","description":"Run the app's full extraction pipeline over a shared link (TikTok, Instagram, Reels, YouTube, a blog, a maps link) and save what it finds as import candidates on the traveller's account. This is one of only two MCP tools that spends the app owner's AI budget rather than yours: it needs the `ai:import` scope, it counts against your daily import cap, and it is tightly rate-limited. It reads any language. Country comes from reverse geocoding and category from Google's own types — never from a model — so a result is a verified place, not a model's guess.","tags":["ai"],"security":[{"oauth":["ai:import"]}],"x-scope":"ai:import","x-writes":true,"x-uses-app-ai":true,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"The link to extract from"},"text":{"type":"string","description":"Optional pasted caption/description to extract from instead of, or as well as, the link"},"title":{"type":"string","description":"Optional title from the share sheet"},"imageUrl":{"type":"string","description":"With text only: an https picture of the place, kept as its thumbnail (≤3 MB)"},"imageBase64":{"type":"string","description":"With text only: the picture as base64 (e.g. one you generated), ≤3 MB"},"imageIsGenerated":{"type":"boolean","description":"true when that picture is AI-generated — the app labels it"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/report_place_busyness":{"post":{"operationId":"report_place_busyness","summary":"File how busy a place USUALLY is at given local hours, from a source you actually consulted (say which)","description":"File how busy a place USUALLY is at given local hours, from a source you actually consulted (say which). Slots are (weekday 0=Sun..6, hour 0-23, level quiet|moderate|busy|packed). Later reports replace earlier ones for the same slot. The app shows this as 'usually busy — estimate', never as a measurement — so only report what a real source supports.","tags":["community"],"security":[{"oauth":["places:write"]}],"x-scope":"places:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"googlePlaceId":{"type":"string","description":"The Google place id (find_places / get_point carry it)."},"source":{"type":"string","description":"Where this came from — a URL or a named source. Required; \"I guessed\" is not a source."},"slots":{"type":"array","description":"Up to 48 slots per call.","items":{"type":"object","properties":{"weekday":{"type":"number","description":"0=Sunday … 6=Saturday, place-local."},"hour":{"type":"number","description":"Local hour 0–23."},"level":{"type":"string","enum":["quiet","moderate","busy","packed"]}},"required":["weekday","hour","level"]}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["googlePlaceId","source","slots"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_place_busyness":{"post":{"operationId":"get_place_busyness","summary":"Read the community-reported busyness curve for a place: (weekday, hour, level) slots with source and reported_at","description":"Read the community-reported busyness curve for a place: (weekday, hour, level) slots with source and reported_at. Empty means NOBODY has reported — the honest answer is then 'unknown', never a guess.","tags":["community"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"googlePlaceId":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["googlePlaceId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/list_strings_for_review":{"post":{"operationId":"list_strings_for_review","summary":"Serve a batch of the app's UI strings for native-speaker review in one language (fr es pt de it ja ko zh th)","description":"Serve a batch of the app's UI strings for native-speaker review in one language (fr es pt de it ja ko zh th). Each row: key, English source text, the machine translation. Review for naturalness, correctness, and register; file verdicts with report_string_review. Batches of 25; page with offset.","tags":["community"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lang":{"type":"string","description":"Language code to review (not en)."},"offset":{"type":"number","description":"Row offset for paging (default 0)."},"domain":{"type":"string","description":"Optional: limit to one string domain (horizon, replan, plans, …)."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["lang"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/report_string_review":{"post":{"operationId":"report_string_review","summary":"File native-speaker verdicts on reviewed strings","description":"File native-speaker verdicts on reviewed strings. Each item: key, lang, verdict (ok|awkward|wrong|offensive|untranslated), optional suggestion + note. Reviews land in a maintainer queue — they never change live copy directly. Re-reviewing a key replaces your earlier verdict.","tags":["community"],"security":[{"oauth":["places:write"]}],"x-scope":"places:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","description":"Up to 25 verdicts per call.","items":{"type":"object","properties":{"key":{"type":"string"},"lang":{"type":"string"},"verdict":{"type":"string","enum":["ok","awkward","wrong","offensive","untranslated"]},"suggestion":{"type":"string"},"note":{"type":"string"}},"required":["key","lang","verdict"]}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["items"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_task":{"post":{"operationId":"get_task","summary":"Read a task you started for the traveller (your own task reference) and what moved since you last saw it: the trip's pla","description":"Read a task you started for the traveller (your own task reference) and what moved since you last saw it: the trip's plan version then vs now, and whether the traveller talked to Horizon about it in between (a count, never the messages). If anything moved, read the trip again before changing it.","tags":["tasks"],"security":[{"oauth":["tasks:read"]}],"x-scope":"tasks:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"taskRef":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["taskRef"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/continue_task":{"post":{"operationId":"continue_task","summary":"Attach your task to one of the traveller's trips — the same conversation Horizon has on that trip — or update it","description":"Attach your task to one of the traveller's trips — the same conversation Horizon has on that trip — or update it. Keep ONLY: the intent, explicit decisions the traveller made, constraints they set, and open questions. Never a transcript or private details. Horizon sees these and continues from them; get_task tells you what Horizon or the traveller changed since.","tags":["tasks"],"security":[{"oauth":["tasks:write"]}],"x-scope":"tasks:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"taskRef":{"type":"string","description":"Your own reference for this task (e.g. your conversation id). One task per trip."},"tripId":{"type":"string"},"intent":{"type":"string","description":"One sentence: what the traveller wants."},"decisions":{"type":"array","items":{"type":"string"},"description":"Up to 20 short decisions the traveller made (\"keep Kyoto separate\")."},"constraints":{"type":"array","items":{"type":"string"},"description":"Up to 20 constraints (\"two free evenings\")."},"pendingQuestions":{"type":"array","items":{"type":"string"},"description":"Up to 20 open questions."},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["taskRef","tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/create_trip":{"post":{"operationId":"create_trip","summary":"Create a real, private trip for the traveller (the same as New plan in the app; the plan limit of their subscription app","description":"Create a real, private trip for the traveller (the same as New plan in the app; the plan limit of their subscription applies). Cities in order, optional dates (YYYY-MM-DD) and pace (strict | casual | fun). Then add their saved places with update_trip_stops and read the day plan with build_trip_plan. Repeating the same request with the same Idempotency-Key never makes a second trip.","tags":["trips"],"security":[{"oauth":["trips:manage"]}],"x-scope":"trips:manage","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"cities":{"type":"array","items":{"type":"string"}},"origin":{"type":"string","description":"Leaving from"},"startDate":{"type":"string"},"endDate":{"type":"string"},"pace":{"type":"string","enum":["strict","casual","fun"]},"legNights":{"type":"array","items":{"type":"number"},"description":"Nights per city, in order (optional)"},"travelers":{"type":"number"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["title"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/update_trip":{"post":{"operationId":"update_trip","summary":"Change a trip's details — title, dates (YYYY-MM-DD), pace (strict | casual | fun), cities in order and nights per city, ","description":"Change a trip's details — title, dates (YYYY-MM-DD), pace (strict | casual | fun), cities in order and nights per city, leaving from. Undoable: it is snapshotted first (undo_plan_change puts it back). Over the REST API send \"expectedVersion\" (from get_trip_state) so a newer plan is never overwritten. Only the trip's owner.","tags":["trips"],"security":[{"oauth":["trips:manage"]}],"x-scope":"trips:manage","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"title":{"type":"string"},"startDate":{"type":"string"},"endDate":{"type":"string"},"pace":{"type":"string","enum":["strict","casual","fun"]},"cities":{"type":"array","items":{"type":"string"}},"legNights":{"type":"array","items":{"type":"number"}},"origin":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_trip_state":{"post":{"operationId":"get_trip_state","summary":"One trip as it stands now: title, dates, pace, cities and nights, leaving from, whether it is a road trip (and its agree","description":"One trip as it stands now: title, dates, pace, cities and nights, leaving from, whether it is a road trip (and its agreed drive), its plan version (send it as expectedVersion when you change the trip), and its last changes. Read this before changing a trip someone else may have edited.","tags":["trips"],"security":[{"oauth":["trips:read"]}],"x-scope":"trips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/undo_plan_change":{"post":{"operationId":"undo_plan_change","summary":"Undo the most recent change to a trip's plan — whoever made it (you, Horizon, or the traveller in the app)","description":"Undo the most recent change to a trip's plan — whoever made it (you, Horizon, or the traveller in the app). Puts back the stops, dates, cities, nights, pace, drive and stop states as they were. The plan version moves on.","tags":["trips"],"security":[{"oauth":["trips:manage"]}],"x-scope":"trips:manage","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/list_collections":{"post":{"operationId":"list_collections","summary":"The traveller's own collections: id, title, emoji, how many places each holds.","description":"The traveller's own collections: id, title, emoji, how many places each holds.","tags":["collections"],"security":[{"oauth":["collections:read"]}],"x-scope":"collections:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/create_collection":{"post":{"operationId":"create_collection","summary":"Create a new, private collection for the traveller.","description":"Create a new, private collection for the traveller.","tags":["collections"],"security":[{"oauth":["collections:write"]}],"x-scope":"collections:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"emoji":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["title"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/rename_collection":{"post":{"operationId":"rename_collection","summary":"Rename one of the traveller's own collections.","description":"Rename one of the traveller's own collections.","tags":["collections"],"security":[{"oauth":["collections:write"]}],"x-scope":"collections:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"collectionId":{"type":"string"},"title":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["collectionId","title"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/add_to_collection":{"post":{"operationId":"add_to_collection","summary":"Add some of the traveller's own saved places to one of their collections","description":"Add some of the traveller's own saved places to one of their collections. A place already there is left as it is (never doubled); its notes and other collections are untouched.","tags":["collections"],"security":[{"oauth":["collections:write"]}],"x-scope":"collections:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"collectionId":{"type":"string"},"pointIds":{"type":"array","items":{"type":"string"}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["collectionId","pointIds"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/remove_from_collection":{"post":{"operationId":"remove_from_collection","summary":"Take some places OUT of one of the traveller's collections","description":"Take some places OUT of one of the traveller's collections. The saved places themselves are never deleted — they stay in the traveller's saves and in their other collections.","tags":["collections"],"security":[{"oauth":["collections:write"]}],"x-scope":"collections:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"collectionId":{"type":"string"},"pointIds":{"type":"array","items":{"type":"string"}},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["collectionId","pointIds"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/find_guides":{"post":{"operationId":"find_guides","summary":"Search published travel guides the community can see (words like a city, a theme, @author)","description":"Search published travel guides the community can see (words like a city, a theme, @author). Each comes with its author for attribution. Private and friends-only guides are never returned.","tags":["guides"],"security":[{"oauth":["guides:read"]}],"x-scope":"guides:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string"},"limit":{"type":"number"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":[]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/propose_road_trip":{"post":{"operationId":"propose_road_trip","summary":"Work out a road trip from a start and a destination: the driving days at the pace's daily limit, a real town to sleep in","description":"Work out a road trip from a start and a destination: the driving days at the pace's daily limit, a real town to sleep in each night, km and hours from the router, ferries and borders, and how the drivers share the wheel. A PREVIEW — nothing is saved (save_road_trip saves exactly this). The free map services answer about once a second: \"pending\" means ask again — what is done comes back at once.","tags":["roadtrips"],"security":[{"oauth":["roadtrips:read"]}],"x-scope":"roadtrips:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string","description":"Where the drive starts (a town or city)"},"destination":{"type":"string"},"stops":{"type":"array","items":{"type":"string"},"description":"Optional places on the way, in order"},"drivers":{"type":"number","description":"1, 2 or 3 people who drive"},"pace":{"type":"string","enum":["relaxed","balanced","sooner"],"description":"relaxed ≤6 h at the wheel a day · balanced ≤8 h · sooner ≤10 h"},"backRoads":{"type":"boolean","description":"Avoid motorways (slower, scenic)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["start","destination"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/save_road_trip":{"post":{"operationId":"save_road_trip","summary":"Save a road trip as a real, private trip — the drive Horizon works out for these inputs (the same as propose_road_trip; ","description":"Save a road trip as a real, private trip — the drive Horizon works out for these inputs (the same as propose_road_trip; it is worked out again here, never taken from your copy), with the way home: \"mirror\" (drive back the same way), \"fly\" (an unbooked flight home) or \"keep_going\" (the trip ends there). Dates YYYY-MM-DD are optional. The plan limit of their subscription applies.","tags":["roadtrips"],"security":[{"oauth":["trips:manage"]}],"x-scope":"trips:manage","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string","description":"Where the drive starts (a town or city)"},"destination":{"type":"string"},"stops":{"type":"array","items":{"type":"string"},"description":"Optional places on the way, in order"},"drivers":{"type":"number","description":"1, 2 or 3 people who drive"},"pace":{"type":"string","enum":["relaxed","balanced","sooner"],"description":"relaxed ≤6 h at the wheel a day · balanced ≤8 h · sooner ≤10 h"},"backRoads":{"type":"boolean","description":"Avoid motorways (slower, scenic)"},"title":{"type":"string"},"startDate":{"type":"string"},"endDate":{"type":"string"},"wayHome":{"type":"string","enum":["mirror","fly","keep_going"]},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["start","destination","wayHome"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/use_guide":{"post":{"operationId":"use_guide","summary":"Copy a published guide (an id from find_guides) into a NEW private trip of the traveller's own — its places in the guide","description":"Copy a published guide (an id from find_guides) into a NEW private trip of the traveller's own — its places in the guide's day order, with credit to the author. Places the traveller already saved are reused, not duplicated; a place the author published without a location is left out and counted, never guessed. A road-trip guide becomes a road trip. Not copied: the author's dates, budget, writing or photos. The plan limit applies.","tags":["guides"],"security":[{"oauth":["trips:manage"]}],"x-scope":"trips:manage","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"guideId":{"type":"string"},"title":{"type":"string","description":"Name for the new trip (default: the guide's title)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["guideId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/find_events":{"post":{"operationId":"find_events","summary":"Events Finding Horizons already has on record for a city (and optional country, dates YYYY-MM-DD, search words): title, ","description":"Events Finding Horizons already has on record for a city (and optional country, dates YYYY-MM-DD, search words): title, date and time, venue, status, whether free, and the official link. Only stored, real listings — when none are on record for that place and those dates, it says so; it never invents one.","tags":["events"],"security":[{"oauth":["events:read"]}],"x-scope":"events:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"city":{"type":"string"},"country":{"type":"string","description":"ISO-2 preferred"},"start":{"type":"string"},"end":{"type":"string"},"q":{"type":"string"},"limit":{"type":"number"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/save_event":{"post":{"operationId":"save_event","summary":"Save an event (an eventId from find_events) to the traveller's places, exactly as Save does in the app — saving is not b","description":"Save an event (an eventId from find_events) to the traveller's places, exactly as Save does in the app — saving is not buying a ticket. Saving the same event again does nothing new.","tags":["events"],"security":[{"oauth":["events:write"]}],"x-scope":"events:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventId":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["eventId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/add_event_to_trip":{"post":{"operationId":"add_event_to_trip","summary":"Add an event (eventId from find_events) to a day of one of the traveller's trips (date YYYY-MM-DD, optional startTime/en","description":"Add an event (eventId from find_events) to a day of one of the traveller's trips (date YYYY-MM-DD, optional startTime/endTime HH:MM). If it clashes with something already that day, NOTHING is added: you get the clash in plain words and the options — ask the traveller, then call again with their choice (add_anyway to add over it). Owner or editor of the trip only.","tags":["events"],"security":[{"oauth":["trips:write"]}],"x-scope":"trips:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventId":{"type":"string"},"tripId":{"type":"string"},"date":{"type":"string"},"startTime":{"type":"string"},"endTime":{"type":"string"},"choice":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["eventId","tripId","date"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/list_future_chapters":{"post":{"operationId":"list_future_chapters","summary":"The places the traveller saved in Next Chapter (future trips, relocations, someday dreams), newest first, with their tag","description":"The places the traveller saved in Next Chapter (future trips, relocations, someday dreams), newest first, with their tag.","tags":["chapters"],"security":[{"oauth":["chapters:read"]}],"x-scope":"chapters:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/save_future_chapter":{"post":{"operationId":"save_future_chapter","summary":"Save a place to the traveller's Next Chapter: a country (name or ISO-2), optionally a city, and optionally one tag: Next","description":"Save a place to the traveller's Next Chapter: a country (name or ISO-2), optionally a city, and optionally one tag: Next trip, Potential relocation, Nomad route, Retirement dream, Someday. Saving one that is already there just updates its tag. (Removing is done in the app.)","tags":["chapters"],"security":[{"oauth":["chapters:write"]}],"x-scope":"chapters:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string"},"city":{"type":"string"},"tag":{"type":"string","enum":["Next trip","Potential relocation","Nomad route","Retirement dream","Someday"]},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["country"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_trip_journal":{"post":{"operationId":"get_trip_journal","summary":"The traveller's own journal entries on one of their trips, oldest first: kind, date, title and text (no photos).","description":"The traveller's own journal entries on one of their trips, oldest first: kind, date, title and text (no photos).","tags":["journals"],"security":[{"oauth":["journal:read"]}],"x-scope":"journal:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}},"required":["tripId"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/add_journal_note":{"post":{"operationId":"add_journal_note","summary":"Add a PRIVATE note to the traveller's journal on one of their trips, in THEIR words: write only what they told you happe","description":"Add a PRIVATE note to the traveller's journal on one of their trips, in THEIR words: write only what they told you happened or what they asked you to note — never turn a planned stop into a memory, never add an opinion they did not give. Optional date YYYY-MM-DD and title. Only the traveller sees it until they share it in the app.","tags":["journals"],"security":[{"oauth":["journal:write"]}],"x-scope":"journal:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tripId":{"type":"string"},"text":{"type":"string"},"title":{"type":"string"},"date":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["tripId","text"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_preferences":{"post":{"operationId":"get_my_preferences","summary":"The travel preferences the traveller set in Explorer DNA (interests loved or passed, budget, walking, pace sliders, excl","description":"The travel preferences the traveller set in Explorer DNA (interests loved or passed, budget, walking, pace sliders, exclusions). Only answers they actually gave — nothing filled in. Read only: they change these in the app.","tags":["preferences"],"security":[{"oauth":["preferences:read"]}],"x-scope":"preferences:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/remind_me":{"post":{"operationId":"remind_me","summary":"WRITE: set a reminder that arrives as a notification on the traveller's phone at a time they choose — only when THEY ask","description":"WRITE: set a reminder that arrives as a notification on the traveller's phone at a time they choose — only when THEY ask (\"remind me at 7 to leave for the ferry\"). Give `text` (≤140 characters; it shows on a locked phone, so no links, e-mails, booking codes or long numbers) and `at` as an ISO time WITH their UTC offset for where they will be (e.g. 2026-10-20T07:00:00-04:00) — ask if you do not know. Optionally tie it to a trip (tripId) or a saved place (pointId): a tap opens it. Quiet hours delay it rather than drop it; their notification settings still apply. Shown as \"Reminder · <your name>\".","tags":["reminders"],"security":[{"oauth":["reminders:write"]}],"x-scope":"reminders:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"What to remind them of, in their language"},"at":{"type":"string","description":"ISO 8601 time with offset, e.g. 2026-10-20T07:00:00-04:00"},"tripId":{"type":"string","description":"Optional: one of their trips (a tap opens it)"},"pointId":{"type":"string","description":"Optional: one of their saved places (a tap opens it)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["text","at"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/list_my_reminders":{"post":{"operationId":"list_my_reminders","summary":"READ: the reminders still waiting to arrive on the traveller's phone (set by any of their assistants), soonest first, wi","description":"READ: the reminders still waiting to arrive on the traveller's phone (set by any of their assistants), soonest first, with ids for cancel_reminder.","tags":["reminders"],"security":[{"oauth":["reminders:write"]}],"x-scope":"reminders:write","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/cancel_reminder":{"post":{"operationId":"cancel_reminder","summary":"WRITE: cancel one of the traveller's waiting reminders by id (from list_my_reminders), when they ask","description":"WRITE: cancel one of the traveller's waiting reminders by id (from list_my_reminders), when they ask. It will not arrive.","tags":["reminders"],"security":[{"oauth":["reminders:write"]}],"x-scope":"reminders:write","x-writes":true,"x-uses-app-ai":false,"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"One per intended change; a repeat never runs twice."},{"name":"X-FH-Task","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Your task reference."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"},"expectedVersion":{"type":"integer","description":"For a change to a trip: the planVersion you last read"}},"required":["id"]}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"202":{"description":"Waiting for the traveller's approval in Finding Horizons"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict: the plan changed since expectedVersion, or the same key is still running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_profile":{"post":{"operationId":"get_my_profile","summary":"What the traveller told the app about themselves: home base, home airport, currency, time zone, language, travel style, ","description":"What the traveller told the app about themselves: home base, home airport, currency, time zone, language, travel style, pace, budget style, who they travel with, interests, dream destinations, passports. Only what they filled in — empty fields are absent. Never their location.","tags":["preferences"],"security":[{"oauth":["preferences:read"]}],"x-scope":"preferences:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_my_visits":{"post":{"operationId":"get_my_visits","summary":"Where the traveller actually went, by THEIR local date: places they checked in at, planned stops they confirmed, and wha","description":"Where the traveller actually went, by THEIR local date: places they checked in at, planned stops they confirmed, and what they wrote in their journal that day. Use for \"where was I last Friday\", \"that place I went to last week\". Dates are 'YYYY-MM-DD' in the traveller's time zone; default is the last 30 days. Being near a place is never a visit.","tags":["places"],"security":[{"oauth":["places:read"]}],"x-scope":"places:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","description":"First day, 'YYYY-MM-DD' (local)"},"to":{"type":"string","description":"Last day, 'YYYY-MM-DD' (local)"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_what_i_learned":{"post":{"operationId":"get_what_i_learned","summary":"What the app has learned about the traveller from what they DID — facts that cleared the evidence bar (never one they ma","description":"What the app has learned about the traveller from what they DID — facts that cleared the evidence bar (never one they marked wrong or deleted), each with its evidence count — and what their own records show: the kinds of places they save most, where, what they actually go to and what they tend to skip, each with its count. Speak of these as tendencies with their evidence, never as certainties.","tags":["preferences"],"security":[{"oauth":["preferences:read"]}],"x-scope":"preferences:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/actions/get_app_guide":{"post":{"operationId":"get_app_guide","summary":"How Finding Horizons works: what a feature does, where it is in the app (tab → screen, as printed), the steps, who has i","description":"How Finding Horizons works: what a feature does, where it is in the app (tab → screen, as printed), the steps, who has it, and a link that opens it. Use for any \"how do I…\", \"where is…\", \"can the app…\" question, and describe ONLY what this guide says.","tags":["reference"],"security":[{"oauth":["reference:read"]}],"x-scope":"reference:read","x-writes":false,"x-uses-app-ai":false,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string","description":"The traveller's question, or a feature name"},"today":{"type":"string","description":"The traveller's own date, YYYY-MM-DD, where the action needs one"}}}}}},"responses":{"200":{"description":"Done — { ok, api, action, result, card, operation? }"},"400":{"description":"Bad body / missing Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope not granted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The action said no","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}