APIHub
Back to Explore

CruiseFeed

Structured cruise inventory: lines, ships, sailings, ports, itineraries, pricing.

Transportation
none
HTTPS
CORS: Unknown
Description enriched
Visit official documentation

Latency

1739ms p95

Uptime

100.0% 30d

Playground

Verified

live

Endpoints

REST · JSON
GET

/healthz

Health check

GET

/v1/account/wallet

Pay-as-you-go balance and history

  • authorization (header)
POST

/v1/account/wallet/topup

Buy pay-as-you-go credit

  • authorization (header)
POST

/v1/account/portal

Open the Stripe Billing Portal

  • authorization (header)
GET

/v1/account/webhooks

Webhook endpoints registered by the caller

  • authorization (header)
POST

/v1/account/webhooks

Register a webhook endpoint (Sync and Managed plans)

  • authorization (header)
GET

/v1/cruises

List & filter cruises

  • cruise_line (query)
  • ship_name (query) — Vessel name. Matched on the normalized name, so spelling variants agree: 'Volendam', 'ms Volendam' and 'MS VOLENDAM' return the same sailings, as do 'Scenic Eclipse II' and 'Scenic Eclipse 2'.
  • embark_port (query) — Substring match on the embarkation port.
  • region (query) — Substring match on the region. The value is canonicalized first, so `Mittelmeer` and `Mediterranean` find the same sailings. Rows whose source published something that is not a region (a route, a sailing title, a product category) store null and match nothing.
  • departure_from (query)
  • departure_to (query)
  • min_price (query) — Lead-in fare at or above this amount. Prices are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them — so this bound compares numbers that are not the same quantity unless you also pin `currency`. The medians differ by more than the filter does: USD 6,860, EUR 1,599, GBP 1,160.
  • max_price (query) — Lead-in fare at or below this amount. Prices are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them — so this bound compares numbers that are not the same quantity unless you also pin `currency`. The medians differ by more than the filter does: USD 6,860, EUR 1,599, GBP 1,160.
  • min_price_per_night (query) — Lead-in fare per night at or above this amount. Per-night amounts are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them unless you set `price_in` — so an unpinned bound compares numbers that are not the same quantity. Measured over 1,000 upcoming sailings on 2026-08-13: USD 71.6%, EUR 16.6%, GBP 11.8%. `price_per_night` is the sailing's LEAD-IN fare over its length, so it is the cheapest cabin per night rather than a like-for-like room; each entry in `fares` carries its own per-night figure when you need one category. A sailing with no fare or no length carries no `price_per_night` and is excluded by either bound, the way a priceless sailing is excluded by `min_price`. /v1/coverage reports how much of the catalogue populates it.
  • max_price_per_night (query) — Lead-in fare per night at or below this amount. This is the bound that makes 'best value' answerable across lengths: a 14-night sailing at 3,500 and a 7-night one at 2,100 rank the other way round per night, so no bound on the TOTAL fare expresses it. Per-night amounts are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them unless you set `price_in` — so an unpinned bound compares numbers that are not the same quantity. Measured over 1,000 upcoming sailings on 2026-08-13: USD 71.6%, EUR 16.6%, GBP 11.8%. `price_per_night` is the sailing's LEAD-IN fare over its length, so it is the cheapest cabin per night rather than a like-for-like room; each entry in `fares` carries its own per-night figure when you need one category. A sailing with no fare or no length carries no `price_per_night` and is excluded by either bound, the way a priceless sailing is excluded by `min_price`. /v1/coverage reports how much of the catalogue populates it.
  • price_in (query) — Currency that `min_price`/`max_price` and `min_price_per_night`/`max_price_per_night` are expressed in. Without it, those bounds compare each sailing's OWN quoted number, so one bound ranges over four different quantities — the medians are USD 6,860, EUR 1,599 and GBP 1,160, which is why `max_price=2000` alone returns nearly every euro sailing and almost no dollar one. With it, the bound is converted at the ECB reference rate and compared like with like. Sailings whose price cannot be converted are excluded, exactly as a sailing with no price is. Quoted prices in the response are never rewritten: `price_amount` and `price_currency` stay as the line published them. A per-night bound is converted the same way and then compared against the converted lead-in fare over the sailing's length, so a sailing whose length is unknown is excluded from it, exactly as one with no price is.
  • currency (query) — Keep only sailings priced in this currency (AUD, EUR, GBP, USD). Pair it with `min_price`/`max_price` or `sort=price` to make those comparisons sound: without it they range over three currencies at once. Sailings with no price at all are excluded, since they are in no currency — use `has_price=false` to find those deliberately.
  • has_price (query) — Restrict to sailings that do (`true`) or do not (`false`) carry a `price_amount`. 13.3% of upcoming sailings have no price, and they are not evenly spread — they cluster in rows we have not re-read recently — so a price filter silently drops them while `sort=price` puts them last. Use `has_price=true` when a fare is required, rather than inferring it from `min_price=0`.
  • sailing_id (query) — Filter to specific sailings by their stable identity. Comma-separated for up to 200 at once. An identity that was retired when two sailings were found to be one still resolves here, so an id you stored earlier keeps working; an identity we never issued matches nothing.
  • min_nights (query)
  • max_nights (query)
  • round_trip (query)
  • cabin_category (query) — Keep only sailings that offer a fare in this cabin category: interior, oceanview, balcony, suite, other. The category is the comparable bucket for each fare's `cabin_class`, which the operator writes in their own words (543 distinct spellings across the catalogue for four rooms) and which is left exactly as they wrote it. A sailing matches if ANY of its fares is in the category, so `cabin_category=balcony` is 'this sailing sells a balcony', not 'its lead-in price is for a balcony'.
  • dedupe (query) — Collapse the same sailing across sources to one row (first-party source wins)
  • seen_since (query) — Only sailings OBSERVED at the source on or after this date (YYYY-MM-DD), matching the `last_seen_at` on each row. This is the freshness filter: `scraped_at` records when a row last CHANGED, so a stable sailing keeps an old `scraped_at` however recently it was re-verified. 96.0% of the served catalogue was observed within the last 48 hours (measured 2026-08-30); `seen_since` is how you exclude the tail deliberately rather than by using `min_price` as a proxy.
  • has_booking_url (query) — Restrict to sailings that do (`true`) or do not (`false`) carry a `booking_url`. About 26% of the catalogue has one — most lines never expose a deep link — so use this when a bookable link is required and expect a much smaller result set. `detail_url` is the more widely populated link at ~59%.
  • include_past (query) — Include sailings that have already departed. Off by default, so a plain listing returns upcoming sailings only. Ignored when departure_from or departure_to is set — an explicit date bound always wins.
  • include_delisted (query) — Include sailings the cruise line has WITHDRAWN from its own catalogue. Off by default, which is what almost every caller wants: these are departures that no longer exist, whose booking links answer with some variant of 'this ship has sailed'. A sailing is only marked this way after it has been missing from many consecutive successful crawls of the line's own catalogue - never because our copy of it is merely old, which is what `seen_since` is for. Set `true` to keep them, e.g. to reconcile a stored id you can no longer find: a withdrawn sailing keeps its `id`, its `sailing_id` and its price history for exactly that reason, and comes back if the line reinstates the departure.
  • sort (query) — One of: departure_date, -departure_date, price, -price, price_per_night, -price_per_night. `price` and `price_per_night` order each sailing's OWN quoted number and the catalogue holds 4 currencies, so pair either with `currency` to rank like with like; `price_in` converts the BOUNDS, never the ordering. Every sort puts nulls last, so `-price` opens on the dearest priced sailing rather than on rows carrying no price at all.
  • limit (query, limit) — Max rows to return.
  • offset (query, offset) — Rows to skip.
  • authorization (header)
GET

/v1/cruises.csv

Export cruises as CSV

  • cruise_line (query)
  • ship_name (query) — Vessel name. Matched on the normalized name, so spelling variants agree: 'Volendam', 'ms Volendam' and 'MS VOLENDAM' return the same sailings, as do 'Scenic Eclipse II' and 'Scenic Eclipse 2'.
  • embark_port (query)
  • region (query)
  • departure_from (query)
  • departure_to (query)
  • min_price (query) — Lead-in fare at or above this amount. Prices are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them — so this bound compares numbers that are not the same quantity unless you also pin `currency`. The medians differ by more than the filter does: USD 6,860, EUR 1,599, GBP 1,160.
  • max_price (query) — Lead-in fare at or below this amount. Prices are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them — so this bound compares numbers that are not the same quantity unless you also pin `currency`. The medians differ by more than the filter does: USD 6,860, EUR 1,599, GBP 1,160.
  • min_price_per_night (query) — Lead-in fare per night at or above this amount. Per-night amounts are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them unless you set `price_in` — so an unpinned bound compares numbers that are not the same quantity. Measured over 1,000 upcoming sailings on 2026-08-13: USD 71.6%, EUR 16.6%, GBP 11.8%. `price_per_night` is the sailing's LEAD-IN fare over its length, so it is the cheapest cabin per night rather than a like-for-like room; each entry in `fares` carries its own per-night figure when you need one category. A sailing with no fare or no length carries no `price_per_night` and is excluded by either bound, the way a priceless sailing is excluded by `min_price`. /v1/coverage reports how much of the catalogue populates it.
  • max_price_per_night (query) — Lead-in fare per night at or below this amount. This is the bound that makes 'best value' answerable across lengths: a 14-night sailing at 3,500 and a 7-night one at 2,100 rank the other way round per night, so no bound on the TOTAL fare expresses it. Per-night amounts are in each sailing's OWN currency (AUD, EUR, GBP, USD), and nothing here converts between them unless you set `price_in` — so an unpinned bound compares numbers that are not the same quantity. Measured over 1,000 upcoming sailings on 2026-08-13: USD 71.6%, EUR 16.6%, GBP 11.8%. `price_per_night` is the sailing's LEAD-IN fare over its length, so it is the cheapest cabin per night rather than a like-for-like room; each entry in `fares` carries its own per-night figure when you need one category. A sailing with no fare or no length carries no `price_per_night` and is excluded by either bound, the way a priceless sailing is excluded by `min_price`. /v1/coverage reports how much of the catalogue populates it.
  • price_in (query) — Currency that `min_price`/`max_price` and `min_price_per_night`/`max_price_per_night` are expressed in. Without it, those bounds compare each sailing's OWN quoted number, so one bound ranges over four different quantities — the medians are USD 6,860, EUR 1,599 and GBP 1,160, which is why `max_price=2000` alone returns nearly every euro sailing and almost no dollar one. With it, the bound is converted at the ECB reference rate and compared like with like. Sailings whose price cannot be converted are excluded, exactly as a sailing with no price is. Quoted prices in the response are never rewritten: `price_amount` and `price_currency` stay as the line published them. A per-night bound is converted the same way and then compared against the converted lead-in fare over the sailing's length, so a sailing whose length is unknown is excluded from it, exactly as one with no price is.
  • currency (query) — Keep only sailings priced in this currency (AUD, EUR, GBP, USD). Pair it with `min_price`/`max_price` or `sort=price` to make those comparisons sound: without it they range over three currencies at once. Sailings with no price at all are excluded, since they are in no currency — use `has_price=false` to find those deliberately.
  • has_price (query) — Restrict to sailings that do (`true`) or do not (`false`) carry a `price_amount`. 13.3% of upcoming sailings have no price, and they are not evenly spread — they cluster in rows we have not re-read recently — so a price filter silently drops them while `sort=price` puts them last. Use `has_price=true` when a fare is required, rather than inferring it from `min_price=0`.
  • sailing_id (query) — Filter to specific sailings by their stable identity. Comma-separated for up to 200 at once. An identity that was retired when two sailings were found to be one still resolves here, so an id you stored earlier keeps working; an identity we never issued matches nothing.
  • min_nights (query)
  • max_nights (query)
  • round_trip (query)
  • cabin_category (query) — Keep only sailings that offer a fare in this cabin category: interior, oceanview, balcony, suite, other. The category is the comparable bucket for each fare's `cabin_class`, which the operator writes in their own words (543 distinct spellings across the catalogue for four rooms) and which is left exactly as they wrote it. A sailing matches if ANY of its fares is in the category, so `cabin_category=balcony` is 'this sailing sells a balcony', not 'its lead-in price is for a balcony'.
  • limit (query, limit) — Max rows to export. Omit to export every matching row (subject to your plan's row cap).
  • offset (query, offset) — Rows to skip before exporting.
  • dedupe (query) — Collapse the same sailing to one row
  • seen_since (query) — Only sailings OBSERVED at the source on or after this date (YYYY-MM-DD), matching the `last_seen_at` on each row. This is the freshness filter: `scraped_at` records when a row last CHANGED, so a stable sailing keeps an old `scraped_at` however recently it was re-verified. 96.0% of the served catalogue was observed within the last 48 hours (measured 2026-08-30); `seen_since` is how you exclude the tail deliberately rather than by using `min_price` as a proxy.
  • has_booking_url (query) — Restrict to sailings that do (`true`) or do not (`false`) carry a `booking_url`. About 26% of the catalogue has one — most lines never expose a deep link — so use this when a bookable link is required and expect a much smaller result set. `detail_url` is the more widely populated link at ~59%.
  • include_past (query) — Include sailings that have already departed. Off by default, matching /v1/cruises. Ignored when departure_from or departure_to is set.
  • include_delisted (query) — Include sailings the cruise line has WITHDRAWN from its own catalogue. Off by default, which is what almost every caller wants: these are departures that no longer exist, whose booking links answer with some variant of 'this ship has sailed'. A sailing is only marked this way after it has been missing from many consecutive successful crawls of the line's own catalogue - never because our copy of it is merely old, which is what `seen_since` is for. Set `true` to keep them, e.g. to reconcile a stored id you can no longer find: a withdrawn sailing keeps its `id`, its `sailing_id` and its price history for exactly that reason, and comes back if the line reinstates the departure.
  • authorization (header)
GET

/v1/cruises/{cruise_id}

Get a cruise by id

  • cruise_id (path, required)
  • include_delisted (query) — Include sailings the cruise line has WITHDRAWN from its own catalogue. Off by default, which is what almost every caller wants: these are departures that no longer exist, whose booking links answer with some variant of 'this ship has sailed'. A sailing is only marked this way after it has been missing from many consecutive successful crawls of the line's own catalogue - never because our copy of it is merely old, which is what `seen_since` is for. Set `true` to keep them, e.g. to reconcile a stored id you can no longer find: a withdrawn sailing keeps its `id`, its `sailing_id` and its price history for exactly that reason, and comes back if the line reinstates the departure.
  • authorization (header)
GET

/v1/cruises/{cruise_id}/history

Get a cruise's price history

  • cruise_id (path, required)
  • authorization (header)
GET

/v1/changes

List recent price changes

  • since (query) — Only changes on/after this date (default: last 30 days)
  • cruise_line (query)
  • sort (query) — One of: -new_date, new_date, pct_change, -pct_change. `pct_change` ranks by the SIZE of the move as a fraction of the old price — ascending opens on the biggest drops, `-pct_change` on the biggest rises — and it ranks every move in the window, not just the page you asked for. It is a percentage rather than a cash delta on purpose: both sides of a move are always in the same currency, so the ratio is comparable across the catalogue's currencies where a raw delta is not. Nulls last.
  • limit (query, limit)
  • offset (query, offset)
  • authorization (header)
GET

/v1/cruise-lines

List cruise lines

  • kind (query) — Optional. Keep only lines of this kind: "retail" (a bookable brand), "owner" (a vessel owner chartering to other brands) or "category" (a browse category, not a company). Omit for every line, which is the historic behaviour.
  • authorization (header)
GET

/v1/ships

List & filter ships

  • q (query) — Optional. Case-insensitive ship-name search.
  • operator (query) — Optional. Filter by operator (partial match).
  • flag_state (query) — Optional. Filter by flag state (partial match).
  • limit (query, limit) — Max rows to return.
  • offset (query, offset) — Rows to skip.
  • authorization (header)
GET

/v1/ships/{ship_id}

Get a ship by IMO

  • ship_id (path, required)
  • authorization (header)
GET

/v1/ports

List departure ports

  • authorization (header)
GET

/v1/ship-names

List ship names that appear on sailings

  • q (query) — Optional. Case-insensitive substring search on the ship name.
  • limit (query, limit) — Maximum names to return (default 50). Raise it to page a whole fleet; leave it for a typeahead.
  • authorization (header)
GET

/v1/stats

Catalogue stats

  • authorization (header)
GET

/v1/coverage

Field coverage contract

POST

/v1/account/key

Get or create the caller's free API key

  • authorization (header)
POST

/v1/account/key/regenerate

Revoke and re-issue the caller's API key

  • authorization (header)
GET

/v1/account

Account overview (plan, key, usage)

  • authorization (header)
GET

/v1/account/terms

Has the caller accepted the current Terms?

  • authorization (header)
POST

/v1/account/terms/accept

Record acceptance of the current Terms

  • authorization (header)
POST

/v1/account/checkout

Start a Stripe Checkout to upgrade

  • authorization (header)
DELETE

/v1/account/webhooks/{endpoint_id}

Remove a webhook endpoint

  • endpoint_id (path, required)
  • authorization (header)
POST

/v1/account/webhooks/{endpoint_id}/enabled

Enable or disable a webhook endpoint

  • endpoint_id (path, required)
  • active (query)
  • authorization (header)
POST

/v1/account/webhooks/{endpoint_id}/test

Send a signed test notification now

  • endpoint_id (path, required)
  • authorization (header)

27 endpoints auto-detected

Authentication

This API requires authentication: apiKey.

curl -X GET \
  "https://cruisefeed.io/healthz"

cruisefeed.io · HTTPS only