{"openapi":"3.0.3","info":{"title":"Goyondo API","description":"API for trip planning, itineraries, and bookings. Use with Bearer token (Clerk session or API key gyd_*).","version":"1.0.0"},"servers":[{"url":"https://goyondo.run"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Clerk session token or Goyondo API key (prefix gyd_)"}},"schemas":{"Error":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string","example":"validation_failed"},"message":{"type":"string"},"details":{"type":"array","items":{}}}}}},"CapabilityDefinition":{"type":"object","properties":{"name":{"type":"string","enum":["describe_capabilities","get_schema","list_trips","get_trip","create_trip","update_trip","regenerate_itinerary","add_activity","update_activity","save_itinerary","list_activities","search_places","get_trip_alerts","delete_activity","get_user_preferences","list_itinerary_segments","get_trip_document","get_trip_expense_summary","submit_group_preferences","submit_group_inspiration","get_group_board","react_to_place","update_place_candidate","update_user_preferences","create_group_trip","list_group_trips","delete_group_trip","join_group_trip","get_group_trip","run_alignment","get_alignment","list_proposals","approve_proposal","reject_proposal","get_edit_log","set_user_preference","get_preference_profile","update_day_notes","delete_trip","get_share_link","create_share_link","disable_share_link","list_fixed_plans","add_fixed_plan","update_fixed_plan","delete_fixed_plan","respond_to_alignment","extend_alignment_rounds","get_alignment_history","get_inspiration","list_community_itineraries","get_community_itinerary"]},"description":{"type":"string"},"auth_required":{"type":"boolean","example":true},"implementation_status":{"type":"string","enum":["implemented"],"description":"Implementation state of the published capability."},"exposed_via":{"type":"array","items":{"type":"string","enum":["task","mcp"]}},"input_schema":{"type":"object","description":"JSON Schema describing capability arguments."}}},"AgentCapabilitiesResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/CapabilityDefinition"}}}},"AgentTaskRequest":{"type":"object","required":["capability"],"properties":{"capability":{"type":"string","enum":["describe_capabilities","get_schema","list_trips","get_trip","create_trip","update_trip","regenerate_itinerary","add_activity","update_activity","save_itinerary","list_activities","search_places","get_trip_alerts","delete_activity","get_user_preferences","list_itinerary_segments","get_trip_document","get_trip_expense_summary","submit_group_preferences","submit_group_inspiration","get_group_board","react_to_place","update_place_candidate","update_user_preferences","create_group_trip","list_group_trips","delete_group_trip","join_group_trip","get_group_trip","run_alignment","get_alignment","list_proposals","approve_proposal","reject_proposal","get_edit_log","set_user_preference","get_preference_profile","update_day_notes","delete_trip","get_share_link","create_share_link","disable_share_link","list_fixed_plans","add_fixed_plan","update_fixed_plan","delete_fixed_plan","respond_to_alignment","extend_alignment_rounds","get_alignment_history","get_inspiration","list_community_itineraries","get_community_itinerary"],"description":"Capability name from GET /api/agent/capabilities"},"arguments":{"type":"object","description":"Arguments for the selected capability. See capability input_schema for exact fields."}}},"AgentTaskResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"result":{"type":"object","description":"Capability-specific response payload."}}},"DeviceAuthRequest":{"type":"object","required":["agent_name","requested_scopes"],"properties":{"agent_name":{"type":"string","example":"My Travel Agent"},"requested_scopes":{"type":"array","items":{"type":"string","enum":["trips:read","trips:write","bookings:read","bookings:write","preferences:read","group_trips:read","group_trips:write"]},"example":["trips:read","trips:write"]}}},"DeviceAuthResponse":{"type":"object","properties":{"user_code":{"type":"string","example":"GYD-A3F9"},"verification_url":{"type":"string","format":"uri","example":"https://goyondo.run/connect"},"verification_uri_complete":{"type":"string","format":"uri","example":"https://goyondo.run/connect?code=GYD-A3F9"},"device_code":{"type":"string","example":"<opaque 64-char hex>"},"expires_in":{"type":"integer","example":600},"interval":{"type":"integer","example":5}}},"DeviceTokenRequest":{"type":"object","required":["device_code"],"properties":{"device_code":{"type":"string","example":"<opaque 64-char hex>"}}},"DeviceTokenResponse":{"type":"object","properties":{"access_token":{"type":"string","example":"gyd_..."},"token_type":{"type":"string","example":"bearer"},"scope":{"type":"string","example":"trips:read trips:write"}}},"DeviceTokenError":{"type":"object","properties":{"error":{"type":"string","example":"authorization_pending"},"error_description":{"type":"string","example":"Waiting for user approval"}}},"Location":{"type":"object","description":"City-level location (home base or trip origin). Give city or airport_iata; the server resolves coordinates, timezone and, when omitted, the nearest airport.","properties":{"city":{"type":"string","example":"Singapore"},"country":{"type":"string"},"airport_iata":{"type":"string","pattern":"^[A-Za-z]{3}$","example":"SIN"}}},"TripCreate":{"type":"object","required":["destination","start_date","end_date"],"properties":{"destination":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"timezone":{"type":"string","description":"Traveler IANA time zone (e.g. America/Los_Angeles). Used to decide whether start_date is in the past. Not stored."},"travelers":{"type":"integer","default":1},"interests":{"type":"array","items":{"type":"string"}},"dietary_restrictions":{"type":"array","maxItems":20,"items":{"type":"string","maxLength":100}},"children":{"type":"object","description":"Optional family composition. The total must leave at least one adult in travelers.","properties":{"infants":{"type":"integer","minimum":0},"children":{"type":"integer","minimum":0},"teens":{"type":"integer","minimum":0}}},"travel_style":{"type":"string","enum":["budget","comfort","luxury"],"description":"Spending level. Older values adventure, cultural and relaxation are still accepted and become comfort plus a matching interest."},"accommodation_type":{"type":"string","nullable":true,"enum":["hotel","hostel","rental","guesthouse","resort","camping",null],"description":"Kind of place to stay, used for suggestions; null for no preference. Older values airbnb and apartment become rental, bb becomes guesthouse."},"skip_ai_generation":{"type":"boolean","default":false},"origin":{"allOf":[{"$ref":"#/components/schemas/Location"}],"nullable":true,"description":"Where the trip starts. Omitted: copied from the user's home base. null: no origin. Never changes the home base. Used to plan Day 1 and the last day around the flight."},"arrival_time":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Local arrival time on Day 1 (HH:MM). Overrides the estimate from the origin."},"fixed_plans":{"type":"array","maxItems":15,"items":{"type":"object"},"description":"Tickets, reservations and must-sees the itinerary is built around. Each: title, timing (exact, day or flexible), category, place_id or city, sessions (one per day attended)."},"departure_time":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"Local departure time on the last day (HH:MM)."},"groupTripCode":{"type":"string","description":"Collaborative group trip code. When present, the server loads board consensus and readiness context for itinerary generation."},"regeneration_mode":{"type":"string","enum":["replace","new_version"],"description":"Required when groupTripCode already has a linked itinerary. replace updates that itinerary; new_version creates and links a fresh trip."}}}}},"paths":{"/api/agent-manifest":{"get":{"summary":"Agent manifest","description":"Machine-readable description of capabilities and how to use the API","security":[],"responses":{"200":{"description":"Agent manifest JSON"}}}},"/api/agent/capabilities":{"get":{"summary":"List executable agent capabilities","description":"Canonical registry of task/MCP capabilities, their implementation status, and JSON argument schemas.","security":[],"responses":{"200":{"description":"Capability registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCapabilitiesResponse"},"example":{"success":true,"capabilities":[{"name":"describe_capabilities","description":"Describe available Goyondo agent capabilities, including input schemas, enum values, and supported interfaces. Use this for introspection before calling other capabilities.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"capability":{"type":"string","description":"Optional capability name to describe. Omit to list every capability."},"name":{"type":"string","description":"Alias for capability."}},"required":[]}},{"name":"get_schema","description":"Return the JSON input schema for one Goyondo capability, or all schemas if no capability is provided.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"capability":{"type":"string","description":"Optional capability name to fetch schema for."},"name":{"type":"string","description":"Alias for capability."}},"required":[]}},{"name":"list_trips","description":"List trips for the authenticated user, newest first. Returns trip id, destination, dates and status. Pages with limit and offset: when has_more is true, call again with offset set to next_offset.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of trips to return (up to 100). Defaults to all trips, or 20 when offset is set."},"offset":{"type":"integer","minimum":0,"description":"Number of trips to skip. Use next_offset from the previous page."}},"required":[]}},{"name":"get_trip","description":"Get a single trip by id including full itinerary (days and activities). trip.origin is where the trip starts and trip.journey the estimated flight hours and time difference (estimates; say \"about\").","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"create_trip","description":"Create a new trip and optionally generate an AI itinerary, which can take up to a few minutes. Returns the created trip and itinerary. Pass fixed_plans for tickets, reservations and must-sees. To write the itinerary yourself, pass skip_ai_generation: true and then call save_itinerary with all the days in one call.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"destination":{"type":"string"},"start_date":{"type":"string","description":"ISO date YYYY-MM-DD"},"end_date":{"type":"string","description":"ISO date YYYY-MM-DD"},"timezone":{"type":"string","description":"Traveler IANA time zone, e.g. America/Los_Angeles. Used to validate that start_date is not in the past."},"travelers":{"type":"integer","default":1},"interests":{"type":"array","items":{"type":"string"}},"dietary_restrictions":{"type":"array","items":{"type":"string"}},"children":{"type":"object","description":"Optional family composition. The total must leave at least one adult in travelers.","properties":{"infants":{"type":"integer","minimum":0},"children":{"type":"integer","minimum":0},"teens":{"type":"integer","minimum":0}}},"travel_style":{"type":"string","enum":["budget","comfort","luxury"],"description":"Spending level. What the trip is about goes in interests."},"accommodation_type":{"type":"string","enum":["hotel","hostel","rental","guesthouse","resort","camping"],"description":"Kind of place to stay, for suggestions only (Goyondo does not book stays). rental means Airbnb or an apartment; guesthouse includes B&Bs. Omit for no preference."},"skip_ai_generation":{"type":"boolean","default":false,"description":"true creates the trip with empty days; fill them with save_itinerary."},"origin":{"type":"object","description":"Where this trip starts, if the user told you. Ask; never infer it from the user's location or language. Omit it to use the user's home base. Setting it never changes the home base. Goyondo uses it to plan Day 1 and the last day around the flight.","properties":{"city":{"type":"string","description":"City, e.g. \"Singapore\" or \"London, UK\""},"country":{"type":"string","description":"Country, optional"},"airport_iata":{"type":"string","description":"3-letter IATA airport code, optional; defaults to the nearest airport to the city"}}},"arrival_time":{"type":"string","description":"Local time the traveller lands on Day 1, HH:MM (24h). Only if the user knows it; otherwise Goyondo estimates from the origin."},"departure_time":{"type":"string","description":"Local time the traveller leaves on the last day, HH:MM (24h). Only if the user knows it."},"fixed_plans":{"type":"array","maxItems":15,"description":"Tickets, reservations and must-sees the itinerary must be built around. Ask the user for the place and timing; never guess them. Get place_id from search_places.","items":{"type":"object","properties":{"title":{"type":"string","description":"Name shown on the trip, max 80 chars. Display only; the planner does not read it."},"timing":{"type":"string","enum":["exact","day","flexible"],"description":"exact: set date and time. day: set date, planner picks the time. flexible: planner picks the day."},"category":{"type":"string","enum":["general","sightseeing","dining","entertainment","shopping","outdoor","cultural"]},"place_id":{"type":"string","description":"From search_places. Provide this or city."},"city":{"type":"string","description":"Only when the place cannot be found, such as a private address."},"sessions":{"type":"array","description":"One entry per day attended, within the trip dates. For a multi-day event, include only the days the user will go. Empty for flexible. start_time and end_time (HH:MM) only for exact.","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"start_time":{"type":"string"},"end_time":{"type":"string"}},"required":["date"]}}},"required":["title","timing"]}},"groupTripCode":{"type":"string","description":"Collaborative group trip code. Server loads readiness and board context."},"regeneration_mode":{"type":"string","enum":["replace","new_version"],"description":"Required when the group already has a linked itinerary."}},"required":["destination","start_date","end_date"]}},{"name":"update_trip","description":"Update trip metadata such as dates, title, traveler count, budget, or status. When start_date or end_date changes, the response includes itinerary_dates showing the first and last day dates after the shift — verify these match your intended dates before proceeding.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"title":{"type":"string","description":"Trip title, max 255 chars"},"destination":{"type":"string","description":"Destination name"},"start_date":{"type":"string","description":"Start date in ISO format YYYY-MM-DD"},"end_date":{"type":"string","description":"End date in ISO format YYYY-MM-DD. May be sent without start_date. If both provided, end_date must be on or after start_date."},"travelers":{"type":"integer","description":"Number of travelers (1–20)"},"dietary_restrictions":{"type":"array","items":{"type":"string"}},"children":{"type":"object","description":"Optional family composition. The total must leave at least one adult in travelers.","properties":{"infants":{"type":"integer","minimum":0},"children":{"type":"integer","minimum":0},"teens":{"type":"integer","minimum":0}}},"budget_per_person":{"type":"number","description":"Budget per person"},"total_budget":{"type":"number","description":"Total budget"},"currency":{"type":"string","description":"3-letter currency code, e.g. USD"},"travel_style":{"type":"string","enum":["budget","comfort","luxury"],"description":"Spending level. What the trip is about goes in interests."},"accommodation_type":{"type":"string","enum":["hotel","hostel","rental","guesthouse","resort","camping"],"description":"Kind of place to stay, for suggestions only (Goyondo does not book stays). rental means Airbnb or an apartment; guesthouse includes B&Bs. Omit for no preference."},"status":{"type":"string","enum":["draft","confirmed","active","completed","cancelled"]},"notes":{"type":"string","description":"Free-text notes, max 2000 chars"},"origin":{"type":"object","description":"Where this trip starts. Ask the user; never infer it. Never changes the home base.","properties":{"city":{"type":"string","description":"City, e.g. \"Singapore\" or \"London, UK\""},"country":{"type":"string","description":"Country, optional"},"airport_iata":{"type":"string","description":"3-letter IATA airport code, optional; defaults to the nearest airport to the city"}}},"clear_origin":{"type":"boolean","description":"true removes the trip's origin."},"arrival_time":{"type":"string","description":"Local time the traveller lands on Day 1, HH:MM (24h). Only if the user knows it; otherwise Goyondo estimates from the origin."},"departure_time":{"type":"string","description":"Local time the traveller leaves on the last day, HH:MM (24h). Only if the user knows it."}},"required":["trip_id"]}},{"name":"regenerate_itinerary","description":"Regenerate the AI itinerary for an existing trip. Replaces AI suggestions only: fixed plans and activities the user or an agent added or edited are kept, and removed activities are not suggested again.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"add_activity","description":"Add an activity to a trip. To assign to a specific day use itinerary_id (UUID from get_trip itinerary days — most reliable), date (YYYY-MM-DD), or day_number (1-based). If none are provided the activity lands on Day 1. The result includes warnings (duplicates, time overlaps) for the new activity; fix them or tell the user. To add several activities, use save_itinerary.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip (from list_trips or get_trip)"},"title":{"type":"string","description":"Activity name, max 255 chars"},"description":{"type":"string","description":"Optional details, max 1000 chars"},"activity_type":{"type":"string","enum":["general","sightseeing","dining","transport","accommodation","entertainment","shopping","outdoor","cultural","business"],"description":"Canonical enum. Common aliases are accepted and normalized, e.g. event -> entertainment, food/dinner -> dining, meeting/conference -> business. Default: general"},"start_time":{"type":"string","description":"Time in HH:MM 24-hour format. HH:MM:SS and ISO-8601 date-time strings are accepted and normalized to HH:MM."},"end_time":{"type":"string","description":"Time in HH:MM 24-hour format. HH:MM:SS and ISO-8601 date-time strings are accepted and normalized to HH:MM."},"duration_minutes":{"type":"integer","description":"Duration in minutes"},"location":{"type":"string","description":"Venue or area name, max 255 chars"},"address":{"type":"string","description":"Full address, max 500 chars"},"estimated_cost":{"type":"number","description":"Cost per person in trip currency"},"priority":{"type":"integer","description":"1 (lowest) to 10 (highest), default 5"},"notes":{"type":"string","description":"Internal notes, max 1000 chars"},"metadata":{"type":"object","properties":{"overnight_transport":{"type":"boolean","description":"Set true only when this transport crosses midnight and is the traveler's overnight plan."}},"additionalProperties":false},"itinerary_id":{"type":"string","description":"UUID of the itinerary day (from get_trip itinerary[].id). Most reliable way to target a specific day."},"day_number":{"type":"integer","description":"Day of the trip (1 = first day). Use itinerary_id or date instead when available."},"date":{"type":"string","description":"Calendar date YYYY-MM-DD. Use itinerary_id instead when available."}},"required":["trip_id","title"]}},{"name":"update_activity","description":"Update an existing activity. All fields except trip_id and activity_id are optional. The result includes warnings (duplicates, time overlaps) involving the activity. Changing anything other than status marks the activity as the user's, so regenerate_itinerary keeps it.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"activity_id":{"type":"string","description":"UUID of the activity (from get_trip itinerary)"},"title":{"type":"string"},"description":{"type":"string"},"activity_type":{"type":"string","enum":["general","sightseeing","dining","transport","accommodation","entertainment","shopping","outdoor","cultural","business"],"description":"Canonical enum. Common aliases are accepted and normalized, e.g. event -> entertainment, food/dinner -> dining, meeting/conference -> business."},"start_time":{"type":"string","description":"HH:MM 24-hour format. HH:MM:SS and ISO-8601 date-time strings are accepted and normalized to HH:MM."},"end_time":{"type":"string","description":"HH:MM 24-hour format. HH:MM:SS and ISO-8601 date-time strings are accepted and normalized to HH:MM."},"duration_minutes":{"type":"integer"},"location":{"type":"string"},"address":{"type":"string"},"estimated_cost":{"type":"number"},"priority":{"type":"integer"},"notes":{"type":"string"},"metadata":{"type":"object","properties":{"overnight_transport":{"type":"boolean","description":"Set true only when this transport crosses midnight and is the traveler's overnight plan."}},"additionalProperties":false},"day_number":{"type":"integer","description":"Move the activity to a different trip day (1 = first day)."},"date":{"type":"string","description":"Move the activity to a specific calendar date YYYY-MM-DD."}},"required":["trip_id","activity_id"]}},{"name":"save_itinerary","description":"Save an itinerary you wrote to an existing trip in one call: every day and its activities. Use it after create_trip with skip_ai_generation: true, or to add to a trip. Append only: existing activities are kept, so call get_trip first and do not resend them. All activities are checked before anything is saved; if any is invalid nothing is saved and the error lists each problem. The result includes warnings (duplicates, time overlaps, repeats across days). Fix them with update_activity or delete_activity, then call get_trip_alerts. Limits: 31 days, 20 activities per day, 150 activities per call.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"days":{"type":"array","minItems":1,"maxItems":31,"description":"Days to fill. Give day_number (1 = first day) or date (YYYY-MM-DD) for each.","items":{"type":"object","properties":{"day_number":{"type":"integer","minimum":1},"date":{"type":"string","description":"YYYY-MM-DD within the trip dates"},"activities":{"type":"array","minItems":1,"maxItems":20,"description":"Same fields as add_activity, without trip_id or day fields. Give start_time and duration_minutes or end_time so overlaps can be checked.","items":{"type":"object","properties":{"title":{"type":"string","description":"Activity name, max 255 chars"},"description":{"type":"string"},"activity_type":{"type":"string","enum":["general","sightseeing","dining","transport","accommodation","entertainment","shopping","outdoor","cultural","business"]},"start_time":{"type":"string","description":"HH:MM 24-hour"},"end_time":{"type":"string","description":"HH:MM 24-hour"},"duration_minutes":{"type":"integer"},"location":{"type":"string","description":"Venue name, used for opening-hours and travel checks"},"address":{"type":"string"},"estimated_cost":{"type":"number"},"notes":{"type":"string"},"metadata":{"type":"object","properties":{"overnight_transport":{"type":"boolean","description":"Set true only when this transport crosses midnight and is the traveler's overnight plan."}},"additionalProperties":false}},"required":["title"]}}},"required":["activities"]}}},"required":["trip_id","days"]}},{"name":"list_activities","description":"List all activities for a trip as a flat array.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"search_places","description":"Find a venue by name and return matching places with their place_id, for use in create_trip fixed_plans.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"query":{"type":"string","description":"Venue name, ideally with the city, e.g. Tokyo Dome"}},"required":["query"]}},{"name":"get_trip_alerts","description":"Check a trip's plan and return its alerts: venues closed or outside opening hours, public holidays, too much travel or backtracking, duplicates, time overlaps, pace, and fixed plans that clash, leave too little travel time, fall outside the trip or could not be placed. Each alert has a scope (activity, day or trip) and affected_activity_ids: the activities it is about, every activity on that day for a day alert, or an empty list for a trip alert. Call it after you finish editing an itinerary and fix or report what it finds. Read-only.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"mode":{"type":"string","enum":["driving","walking","bicycling","transit"],"description":"Travel mode for travel-time checks. Default driving."}},"required":["trip_id"]}},{"name":"delete_activity","description":"Delete an activity from a trip. Regeneration will not suggest it again. If the activity belongs to a fixed plan, that day is removed from the plan, or the whole plan if it was the last day.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string"},"activity_id":{"type":"string"}},"required":["trip_id","activity_id"]}},{"name":"get_user_preferences","description":"Get the authenticated user's travel preferences (interests, dietary, accessibility, etc.). home_base is where they usually travel from (city and airport), or null.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{},"required":[]}},{"name":"list_itinerary_segments","description":"List the flights, hotel stays, car rentals and meetings saved on a trip, usually from pasted confirmations. Works on any trip; an empty list means none were added.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"get_trip_document","description":"Get a document saved on a trip, such as a boarding pass or booking PDF, by id or by name (e.g. boarding pass for flight SQ321). Works on any trip.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"document_id":{"type":"string","description":"UUID of the document (optional if name provided)"},"name":{"type":"string","description":"Search by name substring (optional)"}},"required":["trip_id"]}},{"name":"get_trip_expense_summary","description":"Get the expense summary for a business trip (trip_mode: business), for expense reports. Do not use it for leisure trips.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"submit_group_preferences","description":"Submit group trip preferences for alignment (Group Harmony). Use trip_code from the share link.\nThe caller must already be a member (call join_group_trip first).\nCanonical preferences shape (all keys optional, but alignment only reconciles dates, destination, and budget):\n  dates: { ideal_start: \"YYYY-MM-DD\", ideal_end: \"YYYY-MM-DD\", flexibility_days: integer, blackout_dates: [\"YYYY-MM-DD\"] }\n  destination: { top_choices: [\"City, Country\", ...], veto: [\"City, Country\", ...] }\n  budget: { min: number, max: number } — MUST be an object. A scalar budget (e.g. 2000) is ignored by alignment and treated as 0.\n  accommodation: string   activities: [string, ...]\nFull example:\n  {\"dates\":{\"ideal_start\":\"2026-04-10\",\"ideal_end\":\"2026-04-17\",\"flexibility_days\":3,\"blackout_dates\":[\"2026-04-13\"]},\n   \"destination\":{\"top_choices\":[\"Tokyo, Japan\",\"Osaka, Japan\"],\"veto\":[\"Bangkok, Thailand\"]},\n   \"budget\":{\"min\":1500,\"max\":2500},\"accommodation\":\"hotel\",\"activities\":[\"food tours\",\"museums\"]}\nResponse echoes trip_code and join_url so agents can persist context.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code (e.g. from join URL)"},"preferences":{"type":"object","description":"Canonical preference object — see capability description for the full shape and example.","properties":{"dates":{"type":"object","properties":{"ideal_start":{"type":"string","description":"ISO date YYYY-MM-DD"},"ideal_end":{"type":"string","description":"ISO date YYYY-MM-DD"},"flexibility_days":{"type":"integer"},"blackout_dates":{"type":"array","items":{"type":"string","description":"ISO date YYYY-MM-DD"}}}},"destination":{"type":"object","properties":{"top_choices":{"type":"array","items":{"type":"string"},"description":"e.g. \"Tokyo, Japan\""},"veto":{"type":"array","items":{"type":"string"}}}},"budget":{"type":"object","description":"Object only — a scalar is ignored by alignment and treated as 0.","properties":{"min":{"type":"number"},"max":{"type":"number"}}},"accommodation":{"type":"string"},"activities":{"type":"array","items":{"type":"string"}}}}},"required":["trip_code","preferences"]}},{"name":"submit_group_inspiration","description":"Submit a link, raw text, or explicit place to a group inspiration board. Use trip_code from the group share link. Provide exactly one of url, text, or place.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"},"url":{"type":"string","description":"URL to ingest into the board"},"text":{"type":"string","description":"Raw note/text to ingest into the board (max 5000 chars)"},"place":{"type":"object","description":"Explicit place candidate to add. Shape: { name: string (required), city: string, neighborhood: string, category: enum, blurb: string, confidence: enum }","properties":{"name":{"type":"string","description":"Required. Max 255 chars."},"city":{"type":"string","description":"Max 120 chars"},"neighborhood":{"type":"string","description":"Max 120 chars"},"category":{"type":"string","enum":["food","sight","activity","shopping","nature","nightlife","stay","other"]},"blurb":{"type":"string","description":"Max 500 chars"},"confidence":{"type":"string","enum":["high","medium","low"]}},"required":["name"]}},"required":["trip_code"]}},{"name":"get_group_board","description":"Get the collaborative inspiration board for a group trip, including candidates, reactions, review lane, and readiness signals.\nreadiness signals report whether the board is ready to generate an itinerary, evaluated against readiness_policy:\n  alignment_min_score (number) — required alignment_score before generation\n  min_locked_in_places (integer) — how many \"locked_in\" candidates are required\n  unresolved_splits / failed_imports / review_candidates — each \"block\" | \"warn\" | \"ignore\"\n  creator_override (boolean) — when true, blockers become warnings\nThe response carries ready (boolean), blockers[], warnings[], and counts. The creator sets readiness_policy on the group trip page in Goyondo.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"react_to_place","description":"Vote on a place candidate in a group inspiration board. Agent votes are advisory until the human member confirms them.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"},"candidate_id":{"type":"string","description":"Place candidate UUID"},"vote":{"type":"string","enum":["in","unsure","out"],"description":"One of \"in\", \"unsure\", \"out\"."},"starred":{"type":"boolean","default":false},"reason":{"type":"string","description":"Required (non-empty) when vote is \"out\"; the request is rejected with a 400 otherwise. Max 500 chars."}},"required":["trip_code","candidate_id","vote"]}},{"name":"update_place_candidate","description":"Edit or ignore a review-lane place candidate on a group inspiration board. Agents cannot use action=\"confirm\" (403): a human member confirms the place in Goyondo, which makes it binding. An agent edit corrects the fields and leaves the place in review.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"},"candidate_id":{"type":"string","description":"Place candidate UUID"},"action":{"type":"string","enum":["confirm","edit","ignore"],"description":"\"confirm\" is human-only and accepts the candidate as-is, \"edit\" applies the field overrides below and keeps it in review when an agent calls it, \"ignore\" drops it from the review lane."},"name":{"type":"string","description":"Applies to action=\"edit\". Max 255 chars."},"city":{"type":"string","description":"Applies to action=\"edit\". Max 120 chars."},"neighborhood":{"type":"string","description":"Applies to action=\"edit\". Max 120 chars."},"category":{"type":"string","enum":["food","sight","activity","shopping","nature","nightlife","stay","other"],"description":"Applies to action=\"edit\"."},"blurb":{"type":"string","description":"Applies to action=\"edit\". Max 500 chars."}},"required":["trip_code","candidate_id","action"]}},{"name":"update_user_preferences","description":"Update the authenticated user's travel preferences. Only the fields you send change. You cannot set the home base: if the user asks, pass home_base and Goyondo returns confirmation_required with a confirm_url; give that link to the user so they confirm it in the app. Nothing is saved until they do.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"interests":{"type":"array","items":{"type":"string"}},"travel_style":{"type":"string","enum":["budget","comfort","luxury"],"description":"Spending level. What the trip is about goes in interests."},"accommodation_type":{"type":"string","enum":["hotel","hostel","rental","guesthouse","resort","camping"],"description":"Kind of place to stay, for suggestions only (Goyondo does not book stays). rental means Airbnb or an apartment; guesthouse includes B&Bs. Omit for no preference."},"budget_range":{"type":"string","enum":["under-500","500-1000","1000-2000","2000-5000","over-5000"]},"preferred_destinations":{"type":"array","items":{"type":"string"}},"dietary_restrictions":{"type":"array","items":{"type":"string"}},"default_children":{"type":"object","description":"Default optional family composition for new plans.","properties":{"infants":{"type":"integer","minimum":0},"children":{"type":"integer","minimum":0},"teens":{"type":"integer","minimum":0}}},"accessibility_needs":{"type":"array","items":{"type":"string"}},"home_base":{"type":"object","description":"The user's home city. Agents can't save this directly: sending it returns a confirm_url for the user.","properties":{"city":{"type":"string"},"country":{"type":"string"},"airport_iata":{"type":"string","description":"3-letter IATA code, optional"}}}},"required":[]}},{"name":"create_group_trip","description":"Create a new group trip. Returns trip_code and join_url that members use to join. The caller becomes the creator (and default lead agent). Agent actions on this trip are attributed to the creating member.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"title":{"type":"string","description":"Group trip name, max 255 chars"},"tentative_dates":{"type":"object","description":"Object with start/end date ranges under consideration, e.g. { start: \"2026-04-10\", end: \"2026-04-20\" }"},"destination_options":{"type":"array","items":{"type":"string"},"description":"Array of candidate destination name STRINGS, e.g. [\"Tokyo\", \"Osaka\"]. NOT objects."},"budget_min":{"type":"number","description":"Lower bound of the group budget per person"},"budget_max":{"type":"number","description":"Upper bound of the group budget per person"}},"required":[]}},{"name":"list_group_trips","description":"List all group trips the authenticated user belongs to. Returns an array of { trip_code, status, role, title, id, ... }. role is one of \"creator\" or \"member\". Requires the group_trips:read scope.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{},"required":[]}},{"name":"delete_group_trip","description":"Delete a group trip and its members, preferences, and alignment data. Creator only — other members receive a 403. Members are not notified; a generated itinerary is kept. Requires the group_trips:write scope.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"join_group_trip","description":"Join an existing group trip using its trip_code.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code from the share link"}},"required":["trip_code"]}},{"name":"get_group_trip","description":"Get a group trip by its trip_code, including members and current preferences. Call join_group_trip first if the user has not joined; otherwise this returns 403 not_a_member.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"run_alignment","description":"Trigger Group Harmony alignment — AI reconciles all member preferences (only dates, destination, and budget are reconciled) and produces a consensus plan. Lead agent only. Each call runs the next round; the default max is 2 rounds before the trip moves to escalation_pending (the creator can then call extend-rounds). Every round sets a response_deadline 48h out; the next round can run once every member has responded or that deadline has passed (before then it returns 409 round_still_open with pending_members). Members still silent after the deadline are auto-accepted and returned in timed_out_members.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"get_alignment","description":"Get the latest Group Harmony alignment result for a group trip: consensus_items, conflicts, proposed_destination, proposed_dates, round_number, response_deadline (48h per round), and alignment_score (20/45/70/95 for 0/1/2/3 of dates+destination+budget reconciled). Use get_alignment_history for every round with each member's response, plus pending and timed-out members.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"list_proposals","description":"List pending change proposals for a trip (editors and owners only).","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"approve_proposal","description":"Approve a pending change proposal on a trip.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"proposal_id":{"type":"string","description":"UUID of the proposal"}},"required":["trip_id","proposal_id"]}},{"name":"reject_proposal","description":"Reject a pending change proposal on a trip.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"proposal_id":{"type":"string","description":"UUID of the proposal"}},"required":["trip_id","proposal_id"]}},{"name":"get_edit_log","description":"Get the edit history for a trip, including agent and human changes.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"set_user_preference","description":"Record a travel preference on behalf of the authenticated user. Use this when the user states a preference during conversation (e.g. \"I prefer window seats\", \"I am vegetarian\", \"I like boutique hotels\"). The preference is stored as a signal and their AI travel profile is updated automatically.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"type":{"type":"string","description":"Preference category, e.g. \"dietary_restriction\", \"accommodation_style\", \"pacing\", \"activity_preference\", \"transport_preference\""},"value":{"description":"The preference value (string, number, or boolean)"},"confidence":{"type":"number","description":"0.0–1.0. Default 1.0 for explicit statements."}},"required":["type","value"]}},{"name":"get_preference_profile","description":"Get the authenticated user's AI-rendered travel preference profile (full markdown doc). Useful before creating a trip or making personalised recommendations.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{},"required":[]}},{"name":"update_day_notes","description":"Set or replace the notes for a specific itinerary day. Day notes are free-text and visible in the travel view. Use this to record how a day went, note plan deviations, or add context for a day. Provide either itinerary_id (the day's UUID from get_trip, the same id add_activity uses) or day_number (1-based integer); itinerary_id is preferred when available.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"itinerary_id":{"type":"string","description":"UUID of the itinerary day (from get_trip), as used by add_activity. Preferred over day_number."},"day_id":{"type":"string","description":"Older name for itinerary_id; still accepted."},"day_number":{"type":"integer","description":"Day of the trip (1 = first day). Used to look up the day UUID if itinerary_id is not provided."},"notes":{"type":"string","description":"Free-text notes for the day. Pass an empty string to clear existing notes."}},"required":["trip_id","notes"]}},{"name":"delete_trip","description":"Permanently delete a trip and its itinerary. Only for trips the user owns. Ask the user first, then pass confirm: true; without it nothing is deleted.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"confirm":{"type":"boolean","description":"Must be true. Set it only after the user has confirmed the deletion."}},"required":["trip_id","confirm"]}},{"name":"get_share_link","description":"Get the view-only share link for a trip the user owns: url, whether it is enabled, and when it expires.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"create_share_link","description":"Turn on (or renew) the view-only share link for a trip the user owns and return its url. Give the url to the user to send; Goyondo does not email anyone. Anyone with the link can view the trip until it expires.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"expires_in_days":{"type":"integer","minimum":1,"maximum":30,"description":"Days until the link stops working. Default 7."}},"required":["trip_id"]}},{"name":"disable_share_link","description":"Turn off the view-only share link for a trip the user owns. The old url stops working.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"list_fixed_plans","description":"List a trip's fixed plans (tickets, reservations and must-sees the itinerary is built around), with their ids and any clashes between them.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"}},"required":["trip_id"]}},{"name":"add_fixed_plan","description":"Add a fixed plan (ticket, reservation or must-see) to an existing trip. Ask the user for the place and timing; never guess them. Get place_id from search_places. Clashes are reported by get_trip_alerts; existing activities are never overwritten.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"title":{"type":"string","description":"Name shown on the trip, max 80 chars. Display only; the planner does not read it."},"timing":{"type":"string","enum":["exact","day","flexible"],"description":"exact: set date and time. day: set date, planner picks the time. flexible: planner picks the day."},"category":{"type":"string","enum":["general","sightseeing","dining","entertainment","shopping","outdoor","cultural"]},"place_id":{"type":"string","description":"From search_places. Provide this or city."},"city":{"type":"string","description":"Only when the place cannot be found, such as a private address."},"sessions":{"type":"array","description":"One entry per day attended, within the trip dates. For a multi-day event, include only the days the user will go. Empty for flexible. start_time and end_time (HH:MM) only for exact.","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"start_time":{"type":"string"},"end_time":{"type":"string"}},"required":["date"]}}},"required":["trip_id","title","timing"]}},{"name":"update_fixed_plan","description":"Replace a fixed plan with new details. Send the whole plan (title, timing, place and sessions), not just the changed fields; get the current values from list_fixed_plans.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"fixed_plan_id":{"type":"string","description":"id from list_fixed_plans"},"title":{"type":"string","description":"Name shown on the trip, max 80 chars. Display only; the planner does not read it."},"timing":{"type":"string","enum":["exact","day","flexible"],"description":"exact: set date and time. day: set date, planner picks the time. flexible: planner picks the day."},"category":{"type":"string","enum":["general","sightseeing","dining","entertainment","shopping","outdoor","cultural"]},"place_id":{"type":"string","description":"From search_places. Provide this or city."},"city":{"type":"string","description":"Only when the place cannot be found, such as a private address."},"sessions":{"type":"array","description":"One entry per day attended, within the trip dates. For a multi-day event, include only the days the user will go. Empty for flexible. start_time and end_time (HH:MM) only for exact.","items":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"start_time":{"type":"string"},"end_time":{"type":"string"}},"required":["date"]}}},"required":["trip_id","fixed_plan_id","title","timing"]}},{"name":"delete_fixed_plan","description":"Remove a fixed plan from a trip.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_id":{"type":"string","description":"UUID of the trip"},"fixed_plan_id":{"type":"string","description":"id from list_fixed_plans"}},"required":["trip_id","fixed_plan_id"]}},{"name":"respond_to_alignment","description":"Respond to an open Group Harmony alignment round for the user. decision: accept, reject or partial_accept; feedback is required for reject and partial_accept. A human member's own response overrides their agent's. Get round_id from get_alignment or get_alignment_history.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"},"round_id":{"type":"string","description":"UUID of the round"},"decision":{"type":"string","enum":["accept","reject","partial_accept"]},"accepted_components":{"type":"object","description":"For partial_accept: which parts of the proposal the user accepts.","properties":{"destination":{"type":"boolean"},"dates":{"type":"boolean"},"budget":{"type":"boolean"}}},"feedback":{"type":"string","maxLength":2000,"description":"Why the user rejects or partly accepts. Required unless decision is accept."}},"required":["trip_code","round_id","decision"]}},{"name":"extend_alignment_rounds","description":"Give a Group Harmony trip more alignment rounds after it reaches escalation_pending. Creator only.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"},"additional_rounds":{"type":"integer","minimum":1,"maximum":5}},"required":["trip_code","additional_rounds"]}},{"name":"get_alignment_history","description":"Get every Group Harmony alignment round for a group trip with each member's response, plus the current round's status: who has responded, who is pending and who timed out.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"trip_code":{"type":"string","description":"Group trip code"}},"required":["trip_code"]}},{"name":"get_inspiration","description":"Get destination ideas from Goyondo's Inspiration page. view: featured (editor picks), trending, hidden_gems (reviewed lesser-known places) or calendar (where to go by month). Use it when the user has not picked a destination.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"view":{"type":"string","enum":["featured","trending","hidden_gems","calendar"]},"limit":{"type":"integer","minimum":1,"maximum":12,"description":"Default 6."}},"required":["view"]}},{"name":"list_community_itineraries","description":"Browse published itineraries shared by Goyondo travelers in the Community Hub, newest first. Filter by destination, month, pace or interests. Use get_community_itinerary for the full day-by-day plan.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"destination":{"type":"string"},"month":{"type":"integer","minimum":1,"maximum":12,"description":"Month of travel, 1-12"},"pace":{"type":"string","enum":["light","balanced","packed"]},"interests":{"type":"array","items":{"type":"string","enum":["culture","food","outdoors"]}},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Default 20."},"offset":{"type":"integer","minimum":0}},"required":[]}},{"name":"get_community_itinerary","description":"Get one published Community Hub itinerary with its day-by-day plan. The user can adapt it into their own trip with create_trip and save_itinerary.","auth_required":true,"implementation_status":"implemented","exposed_via":["task","mcp"],"input_schema":{"type":"object","properties":{"listing_id":{"type":"string","description":"UUID from list_community_itineraries"}},"required":["listing_id"]}}]}}}}}}},"/api/agent-auth/device":{"post":{"summary":"Initiate device authorization","description":"Start quick connect. Agents should call this first, then direct the user to verification_uri_complete. Do not start by scraping /connect.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceAuthRequest"}}}},"responses":{"200":{"description":"Device authorization request created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceAuthResponse"}}}},"500":{"description":"Failed to initiate device auth","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenError"}}}}}}},"/api/agent-auth/token":{"post":{"summary":"Poll for approved device token","description":"Poll every interval seconds using the device_code returned by POST /api/agent-auth/device. Treat authorization_pending as a normal in-progress state.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenRequest"}}}},"responses":{"200":{"description":"Approved access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenResponse"}}}},"400":{"description":"Pending, denied, expired, or invalid device code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenError"}}}},"500":{"description":"Failed to poll for token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceTokenError"}}}}}}},"/api/agent/task":{"post":{"summary":"Execute an agent capability","description":"Preferred agent execution path. Fetch GET /api/agent/capabilities first to discover valid capability names and argument schemas.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTaskRequest"}}}},"responses":{"200":{"description":"Capability executed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTaskResponse"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Capability not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/openapi.json":{"get":{"summary":"OpenAPI spec","description":"This document","security":[],"responses":{"200":{"description":"OpenAPI 3.0 JSON"}}}},"/api/trips":{"get":{"summary":"List trips","responses":{"200":{"description":"List of trips"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create trip","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripCreate"}}}},"responses":{"201":{"description":"Trip created (with itinerary if not skip_ai_generation)"},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/trips/{id}":{"get":{"summary":"Get trip and itinerary","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Trip with itinerary. trip.origin is where the trip starts; trip.journey is { estimated_flight_hours, timezone_difference_hours, long_haul, jet_lag } (estimates) or null"},"404":{"description":"Trip not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Update trip","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripCreate"}}}},"responses":{"200":{"description":"Updated trip"},"404":{"description":"Trip not found"}}},"delete":{"summary":"Delete trip","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Trip not found"}}}},"/api/trips/{id}/fixed-plans":{"get":{"summary":"List fixed plans and any overlaps between them","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Fixed plans and conflicts"}}},"post":{"summary":"Add a fixed plan (up to 15 per trip). Place details are looked up from place_id.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Fixed plan"},"400":{"description":"Invalid plan or date outside the trip"},"409":{"description":"Limit reached"},"422":{"description":"Place not found"}}}},"/api/trips/{id}/fixed-plans/{planId}":{"put":{"summary":"Replace a fixed plan and update its activities on the itinerary","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"planId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Fixed plan"},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a fixed plan and its activities","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"planId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Not found"}}}},"/api/trips/{id}/regenerate":{"post":{"summary":"Regenerate itinerary. Replaces AI suggestions only; fixed plans and user or agent edits are kept.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"New itinerary"},"404":{"description":"Trip not found"}}}},"/api/trips/{id}/activities/bulk":{"post":{"summary":"Add many activities in one call (save_itinerary). Append only; nothing is saved if any activity is invalid","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["days"],"properties":{"days":{"type":"array","minItems":1,"maxItems":31,"items":{"type":"object","required":["activities"],"properties":{"day_number":{"type":"integer","minimum":1},"date":{"type":"string","format":"date"},"activities":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","required":["title"]}}}}}}}}}},"responses":{"201":{"description":"Activities added. Body: { success, data: { activities, count }, warnings }"},"400":{"description":"Validation failed; nothing was saved"},"403":{"description":"No edit access to the trip"}}}},"/api/trips/{id}/intelligence":{"get":{"summary":"Plan alerts (opening hours, holidays, travel, duplicates, overlaps, pace) plus travel legs and weather. get_trip_alerts returns the alerts","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","enum":["driving","walking","bicycling","transit"]}}],"responses":{"200":{"description":"Trip intelligence"},"404":{"description":"Trip not found"}}}},"/api/trips/{id}/activities":{"get":{"summary":"Get trip activities","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Activities"}}},"post":{"summary":"Add activity","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"activity_type":{"type":"string"},"start_time":{"type":"string"},"location":{"type":"string"},"day_number":{"type":"integer"}}}}}},"responses":{"201":{"description":"Activity added"}}}},"/api/trips/{id}/activities/{activityId}":{"put":{"summary":"Update activity","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"activityId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated"}}},"delete":{"summary":"Delete activity","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"activityId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"}}}},"/api/users/me/preferences":{"get":{"summary":"Get user preferences","responses":{"200":{"description":"Preferences, including home_base (resolved Location with latitude, longitude and timezone) or null"}}},"put":{"summary":"Update user preferences","description":"Partial update: fields left out keep their saved values. Agents cannot set home_base; they get 403 confirmation_required with error.details[0].confirm_url for the user to confirm in the app.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"interests":{"type":"array","items":{"type":"string"}},"travel_style":{"type":"string","enum":["budget","comfort","luxury"],"description":"Spending level. Older values adventure, cultural and relaxation are still accepted and become comfort plus a matching interest."},"accommodation_type":{"type":"string","nullable":true,"enum":["hotel","hostel","rental","guesthouse","resort","camping",null],"description":"Kind of place to stay, used for suggestions; null for no preference. Older values airbnb and apartment become rental, bb becomes guesthouse."},"budget_range":{"type":"string","enum":["under-500","500-1000","1000-2000","2000-5000","over-5000"]},"preferred_destinations":{"type":"array","items":{"type":"string"}},"dietary_restrictions":{"type":"array","items":{"type":"string"}},"default_children":{"type":"object","description":"Default optional family composition for new plans.","properties":{"infants":{"type":"integer","minimum":0},"children":{"type":"integer","minimum":0},"teens":{"type":"integer","minimum":0}}},"accessibility_needs":{"type":"array","items":{"type":"string"}},"home_base":{"allOf":[{"$ref":"#/components/schemas/Location"}],"nullable":true,"description":"Where the user usually travels from. null clears it. Not settable by agents."}}}}}},"responses":{"200":{"description":"Updated preferences, including home_base"},"400":{"description":"Validation failed, or the home base city was not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"confirmation_required: agents cannot set home_base. error.details[0].confirm_url opens Travel Preferences pre-filled for the user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/airports":{"get":{"summary":"Search airports by IATA code, city or name (public, no auth)","security":[],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}}],"responses":{"200":{"description":"Up to 10 airports: { data: { airports: [{ iata, name, city, country }] } }"},"429":{"description":"Rate limited (60 per minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/trips/{id}/concierge/state":{"get":{"summary":"Get personal Concierge state for the authenticated traveler","description":"Returns this user's settings, context, and location only. Coordinates are never placed on the trip WebSocket room.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"trip_revision","in":"query","schema":{"type":"integer"}},{"name":"context_revision","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Personal concierge state envelope"},"404":{"description":"Trip not found"}}}},"/api/trips/{id}/concierge/settings":{"patch":{"summary":"Update personal Concierge settings","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated settings"},"422":{"description":"Monitoring requires a confirmed transport mode"}}}},"/api/trips/{id}/concierge/location":{"post":{"summary":"Share a foreground GPS sample","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Updated context revision"},"422":{"description":"Invalid or expired sample"}}},"delete":{"summary":"Clear personal location and stop sharing","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Location cleared"}}}},"/api/trips/{id}/concierge/events":{"post":{"summary":"Report a personal status event (idempotent by client_event_id)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Accepted"}}}},"/api/trips/{id}/concierge/evaluate":{"post":{"summary":"Request an evaluation (1/min/user/trip)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Queued"},"429":{"description":"Throttled"}}}},"/api/trips/{id}/concierge/recommendations/{rid}/respond":{"post":{"summary":"Dismiss, keep plan, or snooze a personal recommendation","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"rid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated recommendation"},"409":{"description":"Stale version"},"410":{"description":"Expired"}}}},"/api/trips/{id}/concierge/recommendations/{rid}/apply":{"post":{"summary":"Atomically apply a validated recovery option (editors only)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"rid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":"Applied; duplicate key returns the original result"},"403":{"description":"Viewer cannot apply"},"409":{"description":"Stale trip or option; refresh"},"410":{"description":"Expired or no longer actionable"}}}},"/api/trips/{id}/concierge/recommendations/{rid}/undo":{"post":{"summary":"Undo an applied option if the trip revision is unchanged","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"rid","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Inverse patch applied"},"409":{"description":"Intervening edit; undo refused"}}}},"/api/users/me/push-subscriptions":{"get":{"summary":"List this user's Web Push subscriptions (no p256dh/auth in the response)","responses":{"200":{"description":"Subscriptions and VAPID public key if configured"}}},"post":{"summary":"Create or update a Web Push subscription for the authenticated user","responses":{"200":{"description":"Saved"},"409":{"description":"Endpoint belongs to another user"}}}},"/api/public/trips/{token}":{"get":{"summary":"Get shared trip (no auth)","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Shared trip and itinerary"},"404":{"description":"Not found or expired"}}}},"/api/community/listings":{"get":{"summary":"Browse live Community Hub listings (no auth)","security":[],"parameters":[{"name":"destination","in":"query","description":"City or country, matched against the destination and every stop","schema":{"type":"string","maxLength":100}},{"name":"month","in":"query","description":"Month of the year, 1-12","schema":{"type":"integer","minimum":1,"maximum":12}},{"name":"pace","in":"query","schema":{"type":"string","enum":["light","balanced","packed"]}},{"name":"interests","in":"query","description":"Comma-separated: culture, food, outdoors. Matches any of them.","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Listing summaries"},"404":{"description":"Community Hub is disabled"}}}},"/api/community/listings/{id}":{"get":{"summary":"Get a live Community Hub listing (no auth)","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Listing with day-by-day activities"},"404":{"description":"Not found or not live"}}}}}}