{
  "openapi": "3.1.0",
  "info": {
    "title": "The Vertical Tribe Public API",
    "version": "1.0.0",
    "summary": "Read-only JSON access to The Vertical Tribe's Himalayan trek and expedition catalogue, live departure availability, and the open Indian Himalayan peak dataset.",
    "description": "The Vertical Tribe is a Himalayan trekking and mountaineering operator based in Dehradun, Uttarakhand, India. This API exposes three things an agent may need to answer a question or plan a trip:\n\n1. **Trips** — every published trek and expedition, with altitude, duration, grade, season, group size and price in INR.\n2. **Departures** — the departure dates currently on sale, with status, price and remaining seats. Read live from operational storage.\n3. **Peaks** — a curated dataset of 23 Indian Himalayan climbing peaks (altitude, Alpine French grade, IMF royalty band, permit status, best months), published under CC-BY-4.0 and free to cite.\n\nThe API is public, read-only and needs no authentication or API key. Every response — success or error — is JSON. Every response carries `RateLimit-*` headers so a client can self-throttle; a `429` also carries `Retry-After`.\n\nBooking is deliberately not part of this API. To enquire about a departure, send a human to the trip page or POST a lead to `/api/bookings` (documented at /developers/).",
    "termsOfService": "https://theverticaltribe.com/privacy/",
    "contact": {
      "name": "The Vertical Tribe",
      "email": "hello@theverticaltribe.com",
      "url": "https://theverticaltribe.com/developers/"
    },
    "license": {
      "name": "CC-BY-4.0 (peak dataset)",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://theverticaltribe.com/api/v1",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation, examples and rate-limit conventions",
    "url": "https://theverticaltribe.com/developers/"
  },
  "tags": [
    { "name": "Discovery", "description": "Service index and machine-readable file listing." },
    { "name": "Trips", "description": "Published treks and expeditions." },
    { "name": "Departures", "description": "Departure dates currently on sale." },
    { "name": "Peaks", "description": "Open CC-BY-4.0 dataset of Indian Himalayan climbing peaks." }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getServiceIndex",
        "tags": ["Discovery"],
        "summary": "Service index",
        "description": "Returns every endpoint of this API, every machine-readable file the site publishes (llms.txt, agent-instructions.md, the peak dataset, the sitemap), the rate-limit policy, and the licence and attribution terms. Call this first when you do not know what the site offers.",
        "responses": {
          "200": {
            "description": "Service index.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ServiceIndex" }
              }
            }
          },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/trips": {
      "get": {
        "operationId": "listTrips",
        "tags": ["Trips"],
        "summary": "List treks and expeditions",
        "description": "Lists every published trek and expedition. Use the filters to answer questions like \"which 6,000 m expeditions run in Ladakh under ₹60,000\". Filters combine with AND. An empty result is a 200 with `count: 0`, not a 404.",
        "parameters": [
          { "$ref": "#/components/parameters/TripType" },
          { "$ref": "#/components/parameters/TripRegion" },
          { "$ref": "#/components/parameters/TripDifficulty" },
          { "$ref": "#/components/parameters/MaxPriceInr" }
        ],
        "responses": {
          "200": {
            "description": "Matching trips.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TripList" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidParameter" },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/trips/{slug}": {
      "get": {
        "operationId": "getTrip",
        "tags": ["Trips"],
        "summary": "Get one trek or expedition",
        "description": "Fetches a single trek or expedition by its slug. Slugs are stable and match the public page URL, so `black-peak` corresponds to https://theverticaltribe.com/expeditions/black-peak/. The full itinerary, gear list and FAQs are not in this payload: follow `markdownUrl` for the complete page as Markdown.",
        "parameters": [{ "$ref": "#/components/parameters/TripSlug" }],
        "responses": {
          "200": {
            "description": "The trip.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TripEnvelope" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/departures": {
      "get": {
        "operationId": "listDepartures",
        "tags": ["Departures"],
        "summary": "List departures on sale",
        "description": "Lists the departure dates currently published, read live from operational storage. Seat counts and status change between calls, so this endpoint is cached for 60 seconds only. Unpublished (hidden) departures are never returned. Returns 503 when the deployment has no operational storage configured — fall back to https://theverticaltribe.com/departures.md.",
        "parameters": [{ "$ref": "#/components/parameters/DepartureTrip" }],
        "responses": {
          "200": {
            "description": "Published departures, earliest start date first.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DepartureList" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidParameter" },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": { "$ref": "#/components/responses/UpstreamError" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/peaks": {
      "get": {
        "operationId": "listPeaks",
        "tags": ["Peaks"],
        "summary": "List Indian Himalayan peaks",
        "description": "Lists the open peak dataset: 23 Indian Himalayan climbing peaks with altitude, Alpine French grade, IMF royalty band, permit status, best months and typical duration. Released under CC-BY-4.0 — cite as \"The Vertical Tribe (https://theverticaltribe.com)\". Use the filters to answer \"which open 6,000 m peaks suit a first-timer\".",
        "parameters": [
          { "$ref": "#/components/parameters/PeakOpen" },
          { "$ref": "#/components/parameters/PeakFirstSixThousander" },
          { "$ref": "#/components/parameters/PeakTvtOperated" },
          { "$ref": "#/components/parameters/MaxAltitudeM" }
        ],
        "responses": {
          "200": {
            "description": "Matching peaks.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PeakList" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidParameter" },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/peaks/{id}": {
      "get": {
        "operationId": "getPeak",
        "tags": ["Peaks"],
        "summary": "Get one peak",
        "description": "Fetches a single peak from the open dataset by id, for example `black-peak` or `kang-yatse-2`. The full record — coordinates, first ascent, references and verification date — is at https://theverticaltribe.com/peak-data/peaks.json.",
        "parameters": [{ "$ref": "#/components/parameters/PeakId" }],
        "responses": {
          "200": {
            "description": "The peak.",
            "headers": { "$ref": "#/components/headers/RateLimitSet" },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PeakEnvelope" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "406": { "$ref": "#/components/responses/NotAcceptable" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "TripType": {
        "name": "type",
        "in": "query",
        "required": false,
        "description": "Restrict to walking trips (`trek`) or climbing objectives (`expedition`).",
        "schema": { "type": "string", "enum": ["trek", "expedition"] },
        "example": "expedition"
      },
      "TripRegion": {
        "name": "region",
        "in": "query",
        "required": false,
        "description": "Case-insensitive substring match on the region, e.g. `ladakh`, `nepal`, `uttarakhand`.",
        "schema": { "type": "string", "minLength": 2, "maxLength": 60 },
        "example": "ladakh"
      },
      "TripDifficulty": {
        "name": "difficulty",
        "in": "query",
        "required": false,
        "description": "Exact, case-insensitive match on the published difficulty band.",
        "schema": {
          "type": "string",
          "enum": ["Easy", "Moderate", "Difficult", "Very Difficult"]
        },
        "example": "Moderate"
      },
      "MaxPriceInr": {
        "name": "maxPriceInr",
        "in": "query",
        "required": false,
        "description": "Only trips priced at or below this amount in Indian rupees. Trips with no published price are excluded when this filter is used.",
        "schema": { "type": "integer", "minimum": 1 },
        "example": 60000
      },
      "TripSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "Trip slug, matching the public page URL. List them with `listTrips`.",
        "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "example": "black-peak"
      },
      "DepartureTrip": {
        "name": "trip",
        "in": "query",
        "required": false,
        "description": "Restrict departures to one trip slug.",
        "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "example": "black-peak"
      },
      "PeakOpen": {
        "name": "openPeak",
        "in": "query",
        "required": false,
        "description": "Filter on whether the peak is on the Indian Mountaineering Foundation open-peak list.",
        "schema": { "type": "boolean" },
        "example": true
      },
      "PeakFirstSixThousander": {
        "name": "firstSixThousanderCandidate",
        "in": "query",
        "required": false,
        "description": "Filter on whether the peak passes The Vertical Tribe's first-6,000 m filter (open, non-technical enough, reachable in a normal leave window).",
        "schema": { "type": "boolean" },
        "example": true
      },
      "PeakTvtOperated": {
        "name": "tvtOperated",
        "in": "query",
        "required": false,
        "description": "Filter on whether The Vertical Tribe runs a guided expedition to this peak.",
        "schema": { "type": "boolean" },
        "example": true
      },
      "MaxAltitudeM": {
        "name": "maxAltitudeM",
        "in": "query",
        "required": false,
        "description": "Only peaks at or below this summit altitude in metres.",
        "schema": { "type": "integer", "minimum": 1 },
        "example": 6300
      },
      "PeakId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Peak id from the open dataset. List them with `listPeaks`.",
        "schema": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "example": "black-peak"
      }
    },
    "headers": {
      "RateLimitSet": {
        "description": "Every response carries `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the window resets), plus the legacy `X-RateLimit-*` aliases. Read `RateLimit-Remaining` to self-throttle before you are refused.",
        "schema": { "type": "string" },
        "example": "120;w=60"
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. Sent on 429.",
        "schema": { "type": "integer", "minimum": 1 },
        "example": 37
      }
    },
    "responses": {
      "InvalidParameter": {
        "description": "A query parameter was malformed. The `hint` names the accepted form.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "invalid_parameter",
                "message": "\"type\" must be \"trek\" or \"expedition\", got \"climb\".",
                "status": 400,
                "documentationUrl": "https://theverticaltribe.com/developers/",
                "hint": "Omit the parameter to list both."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource with that identifier, or no endpoint at that path.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "trip_not_found",
                "message": "No trek or expedition with slug \"kilimanjaro\".",
                "status": 404,
                "documentationUrl": "https://theverticaltribe.com/developers/",
                "hint": "List valid slugs at https://theverticaltribe.com/api/v1/trips."
              }
            }
          }
        }
      },
      "NotAcceptable": {
        "description": "The `Accept` header excluded `application/json`. This API only produces JSON.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimited": {
        "description": "Per-IP rate limit exceeded. Wait `Retry-After` seconds.",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" },
          "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitSet" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Rate limit of 120 requests per 60 seconds exceeded.",
                "status": 429,
                "documentationUrl": "https://theverticaltribe.com/developers/",
                "hint": "Wait 37 seconds, then retry. Read RateLimit-Remaining to self-throttle."
              }
            }
          }
        }
      },
      "UpstreamError": {
        "description": "Operational storage could not be read.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "Unavailable": {
        "description": "Live departure storage is not configured on this deployment.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every error this API returns, in one shape.",
        "required": ["error"],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "status", "documentationUrl"],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code. Branch on this, not on the message.",
                "enum": [
                  "invalid_parameter",
                  "trip_not_found",
                  "peak_not_found",
                  "endpoint_not_found",
                  "method_not_allowed",
                  "not_acceptable",
                  "rate_limited",
                  "departures_unavailable",
                  "departures_upstream_error"
                ]
              },
              "message": { "type": "string", "description": "Human-readable explanation of what went wrong." },
              "status": { "type": "integer", "description": "HTTP status code, repeated in the body for clients that lose it." },
              "documentationUrl": { "type": "string", "format": "uri", "description": "Where to read about this API." },
              "hint": { "type": "string", "description": "How to fix the request, when a fix exists." }
            }
          }
        }
      },
      "Trip": {
        "type": "object",
        "description": "A published trek or expedition.",
        "required": ["slug", "name", "type", "region", "difficulty", "season", "description", "url", "markdownUrl"],
        "properties": {
          "slug": { "type": "string", "description": "Stable identifier, matches the page URL." },
          "name": { "type": "string", "description": "Published trip name." },
          "type": { "type": "string", "enum": ["trek", "expedition"], "description": "`trek` walks; `expedition` climbs a named summit." },
          "region": { "type": "string", "description": "Region the trip runs in, e.g. \"Ladakh, India\"." },
          "difficulty": { "type": "string", "enum": ["Easy", "Moderate", "Difficult", "Very Difficult"], "description": "Published difficulty band." },
          "alpineGrade": { "type": "string", "description": "Alpine French grade (F, PD, AD, D…) for climbing objectives. Absent on treks." },
          "summitAltitudeM": { "type": ["integer", "null"], "description": "Highest altitude reached, in metres. Null when the trip publishes no numeric altitude." },
          "altitudeLabel": { "type": "string", "description": "Altitude as published on the page." },
          "durationDays": { "type": ["integer", "null"], "description": "Trip length in days." },
          "durationLabel": { "type": "string", "description": "Duration as published, which may note buffer days." },
          "season": { "type": "string", "description": "Season the trip runs in." },
          "seasonWindow": { "type": "string", "description": "Tighter climbing window when one is published." },
          "groupSize": { "type": "string", "description": "Published group-size cap." },
          "startEnd": { "type": "string", "description": "Start and end city, e.g. \"Leh → Leh\"." },
          "priceInr": { "type": ["integer", "null"], "description": "Per-person price in Indian rupees, or null when priced on request." },
          "priceNote": { "type": "string", "description": "What the price covers, e.g. \"Dehradun to Dehradun\"." },
          "description": { "type": "string", "description": "One-paragraph summary." },
          "shortAnswer": { "type": "string", "description": "Direct answer to \"what is this trip\", written for quoting." },
          "alternateNames": { "type": "array", "items": { "type": "string" }, "description": "Other names the peak or route is known by." },
          "tags": { "type": "array", "items": { "type": "string" }, "description": "Shared taxonomy keys." },
          "lastUpdated": { "type": "string", "format": "date", "description": "Date the trip data was last verified." },
          "url": { "type": "string", "format": "uri", "description": "Canonical HTML page." },
          "markdownUrl": { "type": "string", "format": "uri", "description": "The same page as Markdown — itinerary, inclusions, FAQs, live departures." }
        }
      },
      "TripList": {
        "type": "object",
        "required": ["object", "count", "data"],
        "properties": {
          "object": { "type": "string", "const": "list" },
          "count": { "type": "integer", "description": "Number of trips in `data`." },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Trip" } }
        }
      },
      "TripEnvelope": {
        "type": "object",
        "required": ["object", "data"],
        "properties": {
          "object": { "type": "string", "const": "trip" },
          "data": { "$ref": "#/components/schemas/Trip" }
        }
      },
      "Peak": {
        "type": "object",
        "description": "One Indian Himalayan climbing peak from the CC-BY-4.0 dataset.",
        "required": ["id", "name", "altitudeM", "range", "region", "country", "technicalGrade", "openPeak", "summary", "lastVerified"],
        "properties": {
          "id": { "type": "string", "description": "Stable dataset id." },
          "name": { "type": "string", "description": "Peak name." },
          "altitudeM": { "type": "integer", "description": "Summit altitude in metres." },
          "range": { "type": "string", "description": "Mountain range." },
          "region": { "type": "string", "description": "State or region." },
          "country": { "type": "string", "description": "Country." },
          "technicalGrade": { "type": "string", "description": "Alpine French grade (F through TD)." },
          "imfBand": { "type": ["string", "null"], "enum": ["<6500", "6501-7000", "7001+", null], "description": "IMF royalty altitude band." },
          "imfRoyaltyUsdPartyOf2": { "type": ["integer", "null"], "description": "IMF royalty in USD for a party of two, per the published schedule." },
          "openPeak": { "type": "boolean", "description": "On the IMF open-peak list." },
          "bestMonths": { "type": "array", "items": { "type": "string" }, "description": "Months the peak is normally climbed." },
          "typicalDurationDays": { "type": ["integer", "null"], "description": "Typical round-trip duration in days." },
          "firstSixThousanderCandidate": { "type": "boolean", "description": "Passes The Vertical Tribe's first-6,000 m filter." },
          "tvtOperated": { "type": "boolean", "description": "The Vertical Tribe runs a guided expedition to it." },
          "tvtUrl": { "type": ["string", "null"], "format": "uri", "description": "Trip page when TVT operates it." },
          "summary": { "type": "string", "description": "One-paragraph description of the peak." },
          "lastVerified": { "type": "string", "format": "date", "description": "Date the record was last checked against its sources." }
        }
      },
      "PeakList": {
        "type": "object",
        "required": ["object", "count", "data"],
        "properties": {
          "object": { "type": "string", "const": "list" },
          "count": { "type": "integer" },
          "license": { "type": "string", "const": "CC-BY-4.0" },
          "attribution": { "type": "string", "description": "Attribution string that satisfies the licence." },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Peak" } }
        }
      },
      "PeakEnvelope": {
        "type": "object",
        "required": ["object", "data"],
        "properties": {
          "object": { "type": "string", "const": "peak" },
          "license": { "type": "string", "const": "CC-BY-4.0" },
          "data": { "$ref": "#/components/schemas/Peak" }
        }
      },
      "Departure": {
        "type": "object",
        "description": "One departure currently on sale.",
        "required": ["id", "tripSlug", "tripName", "tripUrl", "startDate", "endDate", "status"],
        "properties": {
          "id": { "type": "string", "description": "Departure id." },
          "tripSlug": { "type": "string", "description": "Slug of the trip this departure belongs to." },
          "tripName": { "type": "string", "description": "Trip name." },
          "tripUrl": { "type": "string", "format": "uri", "description": "Canonical trip page." },
          "startDate": { "type": "string", "format": "date", "description": "First day, ISO 8601." },
          "endDate": { "type": "string", "format": "date", "description": "Last day, ISO 8601." },
          "status": { "type": "string", "enum": ["open", "filling", "sold_out", "cancelled"], "description": "Whether the departure can still be joined." },
          "priceInr": { "type": ["integer", "null"], "description": "Price for this departure in Indian rupees, falling back to the trip price." },
          "seatsTotal": { "type": ["integer", "null"], "description": "Total seats on the departure." },
          "seatsLeft": { "type": ["integer", "null"], "description": "Seats still unbooked." },
          "note": { "type": "string", "description": "Operational note published with the departure." }
        }
      },
      "DepartureList": {
        "type": "object",
        "required": ["object", "count", "data"],
        "properties": {
          "object": { "type": "string", "const": "list" },
          "count": { "type": "integer" },
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Departure" } }
        }
      },
      "ServiceIndex": {
        "type": "object",
        "description": "What this API offers and where the rest of the machine-readable site lives.",
        "required": ["name", "version", "description", "documentationUrl", "openapiUrl", "baseUrl", "endpoints"],
        "properties": {
          "name": { "type": "string" },
          "version": { "type": "string", "description": "API major version." },
          "description": { "type": "string" },
          "documentationUrl": { "type": "string", "format": "uri" },
          "openapiUrl": { "type": "string", "format": "uri", "description": "This specification." },
          "baseUrl": { "type": "string", "format": "uri" },
          "authentication": { "type": "string", "description": "Always \"none\" — the API is public and read-only." },
          "rateLimit": {
            "type": "object",
            "description": "The limit actually enforced, and the headers that report it.",
            "properties": {
              "requests": { "type": "integer" },
              "windowSeconds": { "type": "integer" },
              "scope": { "type": "string" },
              "headers": { "type": "array", "items": { "type": "string" } }
            }
          },
          "endpoints": {
            "type": "array",
            "description": "Every operation, keyed by the same operationId this specification uses.",
            "items": {
              "type": "object",
              "properties": {
                "operationId": { "type": "string" },
                "method": { "type": "string" },
                "url": { "type": "string", "format": "uri" },
                "summary": { "type": "string" }
              }
            }
          },
          "machineReadableFiles": {
            "type": "array",
            "description": "Non-API files an agent may want: llms.txt, agent-instructions.md, the peak dataset, the sitemap.",
            "items": {
              "type": "object",
              "properties": {
                "url": { "type": "string", "format": "uri" },
                "description": { "type": "string" }
              }
            }
          },
          "license": {
            "type": "object",
            "properties": {
              "peakDataset": { "type": "string" },
              "peakDatasetUrl": { "type": "string", "format": "uri" },
              "attribution": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
