{
  "openapi": "3.1.0",
  "info": {
    "title": "Cruise Pricing API",
    "version": "1.0.0",
    "description": "9 cruise lines, one unified schema, 1+ year of daily price history. Built for travel OTAs, affiliate sites, agencies, and financial analysts tracking cruise operators.\n\n**Base URL:** `https://track.cruises/api/public/v1`",
    "contact": {
      "name": "track.cruises support",
      "email": "support@track.cruises",
      "url": "https://track.cruises/data"
    },
    "license": { "name": "Commercial", "url": "https://track.cruises/data/terms" }
  },
  "servers": [
    { "url": "https://track.cruises/api/public/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }, { "rapidApiAuth": [] }],
  "tags": [
    { "name": "cruises", "description": "Search + detail for cruise sailings" },
    { "name": "price-history", "description": "Daily price snapshots — Pro+ moat" },
    { "name": "discovery", "description": "Cruise lines, ships, ports, filters — reference data" },
    { "name": "webhooks", "description": "Push notifications for price-drop events — Pro+" },
    { "name": "coverage", "description": "Public metadata about data freshness + line coverage" }
  ],
  "paths": {
    "/coverage": {
      "get": {
        "tags": ["coverage"],
        "summary": "Data coverage per cruise line",
        "description": "**Public, no auth required.** Returns per-line tracking start date, market list, and sailing counts. Use this to validate data fit before signing up.",
        "security": [],
        "parameters": [
          { "name": "company", "in": "query", "required": false, "schema": { "$ref": "#/components/schemas/CompanyEnum" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": { "Cache-Control": { "schema": { "type": "string", "example": "public, max-age=3600" } } },
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "data": { "type": "array", "items": { "$ref": "#/components/schemas/Coverage" } },
                "request_id": { "type": "string", "example": "01JSAX..." }
              }
            } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/cruises": {
      "get": {
        "tags": ["cruises"],
        "summary": "List cruises with filters",
        "description": "Returns cruise sailings matching filters. Default sort: `departure_date:asc`. **Free tier:** only rows ≥7 days stale.",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/StartingAfter" },
          { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["departure_date", "departure_date:asc", "departure_date:desc", "updated_at", "updated_at:asc", "updated_at:desc"] }, "description": "Single-key sort. Allowed fields: `departure_date`, `updated_at`. Direction defaults to `asc`." },
          { "name": "company", "in": "query", "schema": { "type": "string", "example": "princess,ncl" }, "description": "Comma-separated list. OR semantics." },
          { "name": "locale", "in": "query", "schema": { "type": "string", "example": "en_US" } },
          { "name": "departure_after", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "departure_before", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "min_duration", "in": "query", "schema": { "type": "integer", "minimum": 1 } },
          { "name": "max_duration", "in": "query", "schema": { "type": "integer", "minimum": 1 } },
          { "name": "min_price_eur", "in": "query", "schema": { "type": "number" } },
          { "name": "max_price_eur", "in": "query", "schema": { "type": "number" } },
          { "name": "ship", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated ship names. Exact match, OR semantics." },
          { "name": "port", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated port names from `/ports`. Exact match." },
          { "name": "destination", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated destinations. OR semantics." }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/CruiseList" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/cruises/{id}": {
      "get": {
        "tags": ["cruises"],
        "summary": "Single cruise detail",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "$ref": "#/components/schemas/Cruise" },
              "request_id": { "type": "string" }
            } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/cruises/{id}/price-history": {
      "get": {
        "tags": ["price-history"],
        "summary": "Daily price history for a cruise",
        "description": "**Pro / Enterprise only.** Free tier returns 403. Sorted by `date` ASC.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" } },
          { "name": "cabin_type", "in": "query", "schema": { "type": "string", "example": "BALCONY" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "type": "array", "items": { "$ref": "#/components/schemas/PriceHistorySnapshot" } },
              "has_more": { "type": "boolean" },
              "request_id": { "type": "string" }
            } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/TierInsufficient" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/price-drops": {
      "get": {
        "tags": ["cruises"],
        "summary": "Recent price drops",
        "description": "Sorted by `drop_pct:desc` (biggest drops first). **Free tier:** only drops ≥7 days old.",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/StartingAfter" },
          { "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["drop_pct", "detected_at", "current_price_euro"] } },
          { "name": "company", "in": "query", "schema": { "type": "string" } },
          { "name": "min_drop_pct", "in": "query", "schema": { "type": "number" } },
          { "name": "since", "in": "query", "schema": { "type": "string", "format": "date" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "type": "array", "items": { "$ref": "#/components/schemas/PriceDrop" } },
              "has_more": { "type": "boolean" },
              "next_cursor": { "type": ["string", "null"] },
              "request_id": { "type": "string" }
            } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/cruise-lines": {
      "get": {
        "tags": ["discovery"],
        "summary": "All 9 cruise lines with metadata",
        "description": "Sorted by `cruise_count:desc`. Use for buyer discovery UIs.",
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "type": "array", "items": { "$ref": "#/components/schemas/CruiseLine" } },
              "request_id": { "type": "string" }
            } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/cruise-lines/{company}": {
      "get": {
        "tags": ["discovery"],
        "summary": "Single cruise-line detail",
        "parameters": [{ "name": "company", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/CompanyEnum" } }],
        "responses": {
          "200": { "description": "OK" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/cruise-lines/{company}/destinations": {
      "get": {
        "tags": ["discovery"],
        "summary": "Destinations for a cruise line",
        "description": "Shortcut for filter UIs — returns just `string[]`.",
        "parameters": [{ "name": "company", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/CompanyEnum" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "type": "array", "items": { "type": "string" } },
              "request_id": { "type": "string" }
            } } } } }
        }
      }
    },
    "/filter-options": {
      "get": {
        "tags": ["discovery"],
        "summary": "Valid filter values (companies, locales, destinations, ship names, ports)",
        "description": "Use `?company=<slug>` to scope values to one line.",
        "parameters": [{ "name": "company", "in": "query", "schema": { "$ref": "#/components/schemas/CompanyEnum" } }],
        "responses": {
          "200": { "description": "OK" }
        }
      }
    },
    "/ships": {
      "get": {
        "tags": ["discovery"],
        "summary": "Ship catalog",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/StartingAfter" },
          { "name": "company", "in": "query", "schema": { "$ref": "#/components/schemas/CompanyEnum" } }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/ports": {
      "get": {
        "tags": ["discovery"],
        "summary": "Port catalog",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/StartingAfter" }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/webhooks": {
      "post": {
        "tags": ["webhooks"],
        "summary": "Register a webhook subscription",
        "description": "**Pro / Enterprise only.** The response's `secret` is returned ONCE — store it to verify the HMAC-SHA256 signature on delivery.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object", "required": ["url", "events"],
            "properties": {
              "url": { "type": "string", "format": "uri", "description": "HTTPS URL that will receive POST callbacks. Must not be a private IP, credentials, or HTTP." },
              "events": { "type": "array", "items": { "type": "string", "enum": ["price_drop"] } },
              "filter": { "type": "object", "example": { "company": "princess", "min_drop_pct": 10 } }
            }
          } } }
        },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": {
            "type": "object", "properties": {
              "data": { "type": "object", "properties": {
                "id": { "type": "string", "format": "uuid" },
                "url": { "type": "string" },
                "events": { "type": "array", "items": { "type": "string" } },
                "filter": { "type": "object" },
                "secret": { "type": "string", "description": "HMAC secret — shown ONCE" },
                "created_at": { "type": "string", "format": "date-time" }
              } },
              "request_id": { "type": "string" }
            } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/TierInsufficient" },
          "413": { "description": "Payload too large (body >64KB)" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from https://track.cruises/dashboard/api-keys. Format: `ck_live_<28 chars>` or `ck_test_<...>`."
      },
      "rapidApiAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-RapidAPI-Key",
        "description": "For users subscribing via RapidAPI marketplace. RapidAPI proxies requests with `X-RapidAPI-Proxy-Secret` verified server-side."
      }
    },
    "parameters": {
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 20 }, "description": "Page size. Capped by tier (Free: 10, Pro: 1000, Enterprise: 1000)." },
      "StartingAfter": { "name": "starting_after", "in": "query", "schema": { "type": "string" }, "description": "Opaque cursor from previous response's `next_cursor`." }
    },
    "responses": {
      "CruiseList": { "description": "OK", "content": { "application/json": { "schema": {
        "type": "object", "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Cruise" } },
          "has_more": { "type": "boolean" },
          "next_cursor": { "type": ["string", "null"] },
          "request_id": { "type": "string" }
        } } } } },
      "BadRequest": { "description": "Validation failed", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
      "Unauthorized": { "description": "Missing or invalid auth", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
      "TierInsufficient": { "description": "Endpoint requires a higher tier. Response includes `required_tier`.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
      "NotFound": { "description": "Resource does not exist", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } },
      "RateLimited": { "description": "Rate limit exceeded. Response includes `Retry-After` header and `retry_after_seconds` field.", "content": { "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } } } }
    },
    "schemas": {
      "CompanyEnum": {
        "type": "string",
        "enum": ["princess", "ncl", "celebrity-cruises", "royal-caribbean", "costa", "carnival", "holland-america", "msc", "disney-cruise-line"]
      },
      "Cruise": {
        "type": "object",
        "properties": {
          "cruise_id": { "type": "string" },
          "itinerary_id": { "type": ["string", "null"] },
          "title": { "type": ["string", "null"] },
          "company": { "$ref": "#/components/schemas/CompanyEnum" },
          "locale": { "type": "string" },
          "ship_name": { "type": ["string", "null"] },
          "departure_date": { "type": "string", "format": "date-time" },
          "duration": { "type": ["integer", "null"] },
          "price": { "type": ["number", "null"] },
          "price_euro": { "type": ["number", "null"] },
          "currency": { "type": ["string", "null"] },
          "destinations": { "type": "array", "items": { "type": "string" } },
          "ports_list": { "type": "array", "items": { "type": "object", "properties": { "portName": { "type": "string" }, "dayIndicator": { "type": "string" } } } },
          "itinerary_url": { "type": ["string", "null"] },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "PriceHistorySnapshot": {
        "type": "object",
        "properties": {
          "date": { "type": "string", "format": "date" },
          "price": { "type": ["number", "null"] },
          "price_euro": { "type": ["number", "null"] },
          "currency": { "type": ["string", "null"] },
          "cabin_prices_per_person": { "type": ["object", "null"], "additionalProperties": { "type": "number" } },
          "availability_status": { "type": ["string", "null"] }
        }
      },
      "PriceDrop": {
        "type": "object",
        "properties": {
          "cruise_id": { "type": "string" },
          "company": { "$ref": "#/components/schemas/CompanyEnum" },
          "ship_name": { "type": "string" },
          "title": { "type": "string" },
          "departure_date": { "type": "string", "format": "date-time" },
          "duration": { "type": ["integer", "null"] },
          "destinations": { "type": "array", "items": { "type": "string" } },
          "previous_price_euro": { "type": "number" },
          "current_price_euro": { "type": "number" },
          "drop_amount_euro": { "type": "number" },
          "drop_pct": { "type": "number" },
          "detected_at": { "type": "string", "format": "date-time" },
          "cabin_type": { "type": ["string", "null"] },
          "itinerary_url": { "type": ["string", "null"] }
        }
      },
      "CruiseLine": {
        "type": "object",
        "properties": {
          "company": { "$ref": "#/components/schemas/CompanyEnum" },
          "display_name": { "type": "string" },
          "cruise_count": { "type": "integer" },
          "ship_count": { "type": "integer" },
          "destination_count": { "type": "integer" },
          "destinations": { "type": "array", "items": { "type": "string" } },
          "locales": { "type": "array", "items": { "type": "string" } },
          "earliest_departure": { "type": "string", "format": "date-time" },
          "latest_departure": { "type": "string", "format": "date-time" }
        }
      },
      "Coverage": {
        "type": "object",
        "properties": {
          "company": { "$ref": "#/components/schemas/CompanyEnum" },
          "display_name": { "type": "string" },
          "tracked_since": { "type": "string", "format": "date" },
          "last_updated": { "type": "string", "format": "date" },
          "total_sailings": { "type": "integer" },
          "total_snapshots": { "type": "integer" },
          "avg_snapshot_frequency_days": { "type": "number" },
          "markets": { "type": "array", "items": { "type": "object", "properties": {
            "locale": { "type": "string" }, "market_name": { "type": "string" }, "currency": { "type": "string" }
          } } }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 Problem Details response body.",
        "properties": {
          "type": { "type": "string", "format": "uri" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "code": { "type": "string", "example": "invalid_parameter" },
          "detail": { "type": "string" },
          "param": { "type": "string", "description": "Set when the error relates to a specific parameter" },
          "request_id": { "type": "string", "description": "ULID — include in support requests" },
          "required_tier": { "type": "string", "description": "Set on 403 tier_insufficient responses" },
          "retry_after_seconds": { "type": "integer", "description": "Set on 429 responses" }
        },
        "required": ["type", "title", "status", "code", "request_id"]
      }
    }
  }
}
