{
  "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://cruise-pricing-api1.p.rapidapi.com`\n\n\n\n**Tier ladder (RapidAPI):** BASIC ($0) — evaluation, real-time data but 10 rows/req and 100 req/mo, no price-history; PRO ($49) — real-time, full price-history, 10k req/mo, 100 rows/req; ULTRA ($299) — same as PRO with 100k req/mo and 500 rows/req; MEGA ($1,499) — unlimited quota, 1000 rows/req. See `track.cruises/data` for the full matrix.",
    "contact": {
      "name": "track.cruises support",
      "email": "support@track.cruises",
      "url": "https://track.cruises/data"
    },
    "license": {
      "name": "Commercial",
      "url": "https://track.cruises/terms"
    }
  },
  "servers": [
    {
      "url": "https://cruise-pricing-api1.p.rapidapi.com",
      "description": "Production (RapidAPI relay)"
    }
  ],
  "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": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Company"
          }
        ],
        "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"
          }
        },
        "operationId": "getCoverage"
      }
    },
    "/cruises": {
      "get": {
        "tags": [
          "cruises"
        ],
        "summary": "List cruises with filters",
        "description": "Returns cruise sailings matching filters. Default sort: `departure_date:asc`. **Free tier:** returns the same real-time data as paid tiers, capped at 10 rows per request.",
        "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`. Default when omitted: `departure_date:asc`. Use `sort=updated_at:desc` to surface the most-recently-scraped sailings first."
          },
          {
            "$ref": "#/components/parameters/Company"
          },
          {
            "$ref": "#/components/parameters/Locale"
          },
          {
            "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"
          }
        },
        "operationId": "listCruises"
      }
    },
    "/cruises/{id}": {
      "get": {
        "tags": [
          "cruises"
        ],
        "summary": "Single cruise detail",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "company",
            "in": "query",
            "required": false,
            "description": "Cruise line slug (e.g. `princess`, `holland-america`). Optional. Cruise ids are only unique per cruise line — Princess and Holland America share the same voyage-code format (e.g. `Y731`), so pass `company` to disambiguate those ids. Omitting it preserves the historical behavior.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$",
              "example": "princess"
            }
          }
        ],
        "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"
          }
        },
        "operationId": "getCruise"
      }
    },
    "/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"
            }
          },
          {
            "$ref": "#/components/parameters/Locale"
          },
          {
            "name": "company",
            "in": "query",
            "required": false,
            "description": "Cruise line slug (e.g. `princess`, `holland-america`). Optional. Cruise ids are only unique per cruise line — Princess and Holland America share the same voyage-code format (e.g. `Y731`), so pass `company` to disambiguate those ids. Omitting it preserves the historical behavior.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$",
              "example": "princess"
            }
          }
        ],
        "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"
          }
        },
        "operationId": "getPriceHistory"
      }
    },
    "/price-drops": {
      "get": {
        "tags": [
          "cruises"
        ],
        "summary": "Recent price drops",
        "description": "Recent price drops sorted by `drop_pct:desc` (biggest drops first). No tier-based freshness gate — drops surface in real time for all tiers. The fresh-data paywall lives on `/cruises` and `/cruises/{id}` (free-tier evaluation window) where it makes sense.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "drop_pct",
                "detected_at",
                "current_price_euro"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Company"
          },
          {
            "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"
          }
        },
        "operationId": "listPriceDrops"
      }
    },
    "/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"
          }
        },
        "operationId": "listCruiseLines"
      }
    },
    "/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"
          }
        },
        "operationId": "getCruiseLine"
      }
    },
    "/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"
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "getCruiseLineDestinations"
      }
    },
    "/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": [
          {
            "$ref": "#/components/parameters/Company"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "operationId": "getFilterOptions"
      }
    },
    "/ships": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "Ship catalog",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          },
          {
            "$ref": "#/components/parameters/Company"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "operationId": "listShips"
      }
    },
    "/ports": {
      "get": {
        "tags": [
          "discovery"
        ],
        "summary": "Port catalog",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/StartingAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "operationId": "listPorts"
      }
    }
  },
  "components": {
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 20
        },
        "description": "Page size. Capped by tier — Free: 10, Pro: 100, Ultra: 500, Enterprise: 1000. Requests with a higher `limit` than the tier cap are silently clamped to the cap (200 OK)."
      },
      "StartingAfter": {
        "name": "starting_after",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Opaque cursor from previous response's `next_cursor`."
      },
      "Locale": {
        "name": "locale",
        "in": "query",
        "description": "Filter results to a single market. Determines pricing currency. Coverage varies by cruise line — call `GET /coverage` to see which lines support which locales.",
        "schema": {
          "$ref": "#/components/schemas/LocaleEnum"
        },
        "example": "en_US"
      },
      "Company": {
        "name": "company",
        "in": "query",
        "description": "Filter by cruise line. Call `GET /cruise-lines` for display names + per-line metadata.",
        "schema": {
          "$ref": "#/components/schemas/CompanyEnum"
        }
      }
    },
    "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.\n\n**Note on response format:** Two shapes are possible depending on which layer tripped the limit:\n\n1. **Our backend** (per-key monthly quota, per-key per-minute burst, per-IP burst) — returns the standard `application/problem+json` body below with `Retry-After` header, `code: \"rate_limit_exceeded\"`, and `request_id`.\n\n2. **RapidAPI relay** (per-plan throttle enforced by RapidAPI before requests reach our backend) — returns `application/json` with `{\"message\": \"You have exceeded the rate limit per minute for your plan, <PLAN>, by the API provider\"}`. No `request_id`. Only seen when calling via `cruise-pricing-api1.p.rapidapi.com`.\n\nClient error handlers should tolerate both shapes.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          },
          "application/json": {
            "schema": {
              "type": "object",
              "description": "RapidAPI-relayed throttle response. Only seen via the RapidAPI host.",
              "properties": {
                "message": {
                  "type": "string",
                  "example": "You have exceeded the rate limit per minute for your plan, BASIC, by the API provider"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "CompanyEnum": {
        "type": "string",
        "enum": [
          "princess",
          "ncl",
          "celebrity-cruises",
          "royal-caribbean",
          "costa",
          "carnival",
          "holland-america",
          "msc",
          "disney-cruise-line",
          "aida"
        ]
      },
      "LocaleEnum": {
        "type": "string",
        "description": "Market identifier in `lang_REGION` form. Determines pricing currency and source market for a sailing. Not every cruise line covers every locale — see `GET /coverage` for the per-company matrix.",
        "enum": [
          "de_AT",
          "de_DE",
          "en_AU",
          "en_CA",
          "en_GB",
          "en_US",
          "it_IT",
          "nl_NL"
        ]
      },
      "Cruise": {
        "type": "object",
        "properties": {
          "cruise_id": {
            "type": "string"
          },
          "itinerary_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "company": {
            "$ref": "#/components/schemas/CompanyEnum"
          },
          "locale": {
            "$ref": "#/components/schemas/LocaleEnum"
          },
          "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"
            ]
          },
          "cabin_prices_per_person": {
            "type": "object",
            "nullable": true,
            "description": "Per-cabin per-person pricing in the cruise's local currency. Keys are normalized cabin codes (INTERIOR, OCEANVIEW, BALCONY, MINISUITE, SUITE — plus line-specific tiers like CONCIERGE, AQUA, VISTA_SUITE, NEPTUNE_SUITE, HAVEN). Null when the underlying source didn't provide cabin-level pricing for this sailing. **Only populated by GET /cruises/{id}** — list endpoint GET /cruises omits this field to keep responses lean (call /cruises/{id} for the full breakdown). Costa exception: Costa's cabin-level pricing source has been unavailable since 2026-04-21, so this field is null for Costa sailings tracked after that date — lowest-price tracking (price, price_euro) is unaffected.",
            "additionalProperties": {
              "type": "number"
            },
            "example": {
              "INTERIOR": 1199,
              "OCEANVIEW": 1399,
              "BALCONY": 1799,
              "SUITE": 3499
            }
          },
          "destinations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ports_list": {
            "type": "array",
            "description": "Itinerary stops, ordered by day.",
            "items": {
              "type": "object",
              "required": [
                "port"
              ],
              "properties": {
                "port": {
                  "type": "string",
                  "example": "Barcelona"
                },
                "day": {
                  "type": "integer",
                  "minimum": 1,
                  "nullable": true,
                  "description": "1-indexed day within the itinerary. NULL when the source feed didn't supply a day — Holland America's feed in particular omits per-port day numbers, so all HAL sailings return null here. Buyers should treat null as 'unknown day' rather than zero or one."
                },
                "arrival": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Optional. Not populated for all lines."
                },
                "departure": {
                  "type": "string",
                  "format": "date-time",
                  "description": "Optional. Not populated for all lines."
                }
              }
            }
          },
          "itinerary_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PriceHistorySnapshot": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "locale": {
            "$ref": "#/components/schemas/LocaleEnum"
          },
          "company": {
            "$ref": "#/components/schemas/CompanyEnum"
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "price_euro": {
            "type": [
              "number",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "cabin_prices_per_person": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "number"
            },
            "description": "Per-cabin per-person prices for this snapshot. Keys are normalized cabin codes (INTERIOR, OCEANVIEW, BALCONY, SUITE, plus line-specific tiers). Null when no cabin-level pricing was captured. Costa exception: Costa's cabin-level pricing source has been unavailable since 2026-04-21, so snapshots after that date have null here — price and price_euro are unaffected."
          },
          "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": {
                  "$ref": "#/components/schemas/LocaleEnum"
                },
                "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"
        ]
      },
      "Ship": {
        "type": "object",
        "required": [
          "ship_name",
          "company",
          "sailing_count"
        ],
        "properties": {
          "ship_name": {
            "type": "string",
            "example": "Costa Toscana"
          },
          "company": {
            "$ref": "#/components/schemas/CompanyEnum"
          },
          "sailing_count": {
            "type": "integer",
            "description": "Number of sailings currently in our coverage for this ship.",
            "example": 7215
          },
          "earliest_departure": {
            "type": "string",
            "format": "date-time"
          },
          "latest_departure": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Port": {
        "type": "object",
        "required": [
          "port",
          "sailing_count"
        ],
        "properties": {
          "port": {
            "type": "string",
            "example": "Barcelona, Spain"
          },
          "sailing_count": {
            "type": "integer",
            "description": "Number of sailings touching this port across all lines.",
            "example": 5401
          }
        }
      }
    }
  }
}