{
  "openapi": "3.1.0",
  "info": {
    "title": "Itinerist",
    "version": "1.0.0",
    "summary": "Researched, hour-by-hour travel itineraries, priced per trip.",
    "description": "A four-step surface: create an intake, get a deterministic price, hand a human a checkout link, retrieve the delivered itinerary.\n\nNo tool in this surface can move money. create_checkout returns a Stripe-hosted URL; a human completes the payment.\n\nItinerist researches and plans; it does not book. Nothing is reserved on the traveler's behalf and availability is never guaranteed. Every recommendation carries how to book it and how far ahead. Places are marked researched with the date they were checked — never verified.\n\nNo authentication. The purchase chain is anonymous by design and the itinerary token is the only credential. There is no CAPTCHA on this surface; abuse control is a light per-IP rate limit on quoting.",
    "contact": {
      "url": "https://itinerist.co/agents"
    }
  },
  "servers": [
    {
      "url": "https://itinerist.co"
    }
  ],
  "paths": {
    "/api/intake": {
      "post": {
        "operationId": "create_intake",
        "summary": "Submit a structured travel intake and receive its id.",
        "description": "Step 1 of 4. Validates the intake against the contract and stores it. The intake is deterministic — no model runs here — and it is what every later step references. Returns an intakeId to pass to get_quote.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "destinations",
                  "startDate",
                  "endDate",
                  "party",
                  "prioritiesRanked",
                  "budgetBand"
                ],
                "properties": {
                  "destinations": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Cities or regions, e.g. [\"Rome\", \"Amalfi Coast\"]. May be empty ONLY when helpMeChoose is true."
                  },
                  "helpMeChoose": {
                    "type": "boolean",
                    "default": false,
                    "description": "True when the traveler wants the destination chosen for them."
                  },
                  "tripIntent": {
                    "type": "string",
                    "default": "",
                    "description": "What the trip is really about, in the traveler's words (\"our tenth anniversary\", \"Rio just for Christ the Redeemer\"). The highest-signal personalization field — worth asking for."
                  },
                  "startDate": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD."
                  },
                  "endDate": {
                    "type": "string",
                    "format": "date",
                    "description": "YYYY-MM-DD."
                  },
                  "fixedPoints": {
                    "type": "string",
                    "default": "",
                    "description": "Already-booked or non-negotiable items with their times (\"Hotel Saujana booked Mar 3–6; client dinner Mar 4 8pm\"). Treated as IMMOVABLE and planned around, never rescheduled."
                  },
                  "party": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "adults",
                      "children"
                    ],
                    "properties": {
                      "adults": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "children": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "childrenAges": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 17
                        },
                        "default": []
                      },
                      "mobility": {
                        "enum": [
                          "none",
                          "some_limits",
                          "wheelchair"
                        ],
                        "default": "none"
                      },
                      "dietaryPreferences": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "default": [],
                        "description": "Taste and lifestyle choices (vegetarian, halal). Shapes restaurant selection."
                      },
                      "medicalDietary": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "default": [],
                        "description": "SAFETY information (celiac, nut allergy). Never conflated with preferences — drives an honest-accommodation duty in generation."
                      }
                    }
                  },
                  "prioritiesRanked": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Ordered, most important first."
                  },
                  "pace": {
                    "enum": [
                      "deep",
                      "balanced",
                      "sampling"
                    ],
                    "default": "balanced",
                    "description": "deep = fewer places, longer in each; sampling = cover as much ground as the days allow."
                  },
                  "accommodationStyle": {
                    "enum": [
                      "boutique",
                      "resort",
                      "apartment",
                      "no_preference"
                    ],
                    "default": "no_preference"
                  },
                  "priorVisits": {
                    "enum": [
                      "first_time",
                      "returning"
                    ],
                    "default": "first_time"
                  },
                  "antiPreferences": {
                    "type": "string",
                    "default": "",
                    "description": "Things the traveler never wants to see. Treated as hard constraints, not preferences."
                  },
                  "budgetBand": {
                    "enum": [
                      "comfortable",
                      "premium",
                      "luxury"
                    ]
                  },
                  "anythingElse": {
                    "type": "string",
                    "default": "",
                    "description": "Free-text catch-all. Carries both idiosyncratic dislikes and third-party pointers (\"a coworker raved about Le Coq et Fil\") — named places worth validating."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "intakeId": "3f1c…"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request or failed validation."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    },
    "/api/quote": {
      "post": {
        "operationId": "get_quote",
        "summary": "Compute the price for a stored intake.",
        "description": "Step 2 of 4. The price is DETERMINISTIC — a published formula over trip length, destination count and party composition, with no model involved — so the same intake always returns the same amount. Returns amountCents and the tier. Rate limited per IP; there is no CAPTCHA.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "intakeId"
                ],
                "properties": {
                  "intakeId": {
                    "type": "string",
                    "description": "From create_intake."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "quoteId": "8a2b…",
                  "intakeId": "3f1c…",
                  "tier": "single_city",
                  "amountCents": 1900,
                  "formulaVersion": 1,
                  "escalators": []
                }
              }
            }
          },
          "400": {
            "description": "Malformed request or failed validation."
          },
          "429": {
            "description": "Rate limited per IP. There is no CAPTCHA; retry later."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    },
    "/api/checkout": {
      "post": {
        "operationId": "create_checkout",
        "summary": "Create a Stripe checkout URL for a human to approve. Does NOT take payment.",
        "description": "Step 3 of 4. Returns a Stripe-hosted checkout URL. This tool CANNOT complete a purchase: no money moves until a human opens that URL and approves the payment themselves. Present the link to your user; do not represent the trip as bought until they have completed checkout.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "quoteId",
                  "intakeId"
                ],
                "properties": {
                  "quoteId": {
                    "type": "string",
                    "description": "From get_quote."
                  },
                  "intakeId": {
                    "type": "string",
                    "description": "Must match the intake the quote was priced from."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "url": "https://checkout.stripe.com/c/pay/…"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request or failed validation."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    },
    "/api/itinerary/{token}": {
      "get": {
        "operationId": "get_itinerary",
        "summary": "Retrieve a delivered itinerary by its token.",
        "description": "Step 4 of 4. The token is the only credential and is delivered to the traveler by email after purchase; it is long and unguessable, and cannot be enumerated. Returns the full itinerary: days, hour-by-hour slots, door-to-door transit, booking deadlines, and one alternate for every bookable stop. Every place is researched and date-stamped — never booked on the traveler's behalf, and never marked verified.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The unguessable itinerary token from the delivery email. The only credential this surface uses."
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "tripTitle": "…",
                  "days": [],
                  "bookingTimeline": [],
                  "researchDate": "2026-06-13"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request or failed validation."
          },
          "404": {
            "description": "No itinerary for that token."
          },
          "500": {
            "description": "Server error."
          }
        }
      }
    }
  }
}