Cruise Itinerary

API reference

Cruise Itinerary API

A JSON API over one base URL, https://cruise-itinerary.com/v1. Reads only, no SDK, nothing to install.

Quickstart

1. Try an open endpoint. No key is needed for reference data.

curl https://cruise-itinerary.com/v1/ships/msc-fantasia

2. Create a free account. We email you a sign-in link; there is no password. Your first key is shown once, so copy it then.

3. Send it as a bearer token.

export CI_API_KEY=ci_live_...
curl -H "Authorization: Bearer $CI_API_KEY" \
  'https://cruise-itinerary.com/v1/resolve?q=Port%20Canaveral&type=port'

4. Look up what you got back. Every record has a stable slug in id; use it in later calls and in your own tables.

Authentication

Send the key in either header. Keys start with ci_live_ and are 40 characters long.

Authorization: Bearer ci_live_...
X-API-Key: ci_live_...

With no key you are on the No key tier: 30 calls a minute per IP address, open endpoints only. A key that is malformed, unknown or revoked gets a 401, never a silent downgrade. Keep keys on your server. If one leaks, revoke it from the dashboard and make another.

Responses

One record comes back as data and an object; a list comes back as data and an array. Both carry meta.as_of, the date of the inventory load the answer reflects (currently 2026-09-28).

Single record

{
    "data": {
        "id": "msc-cruises",
        "name": "MSC Cruises"
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

List

{
    "data": [
        {
            "id": "..."
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 50,
        "total": 1234,
        "has_more": true
    }
}

Dates are ISO YYYY-MM-DD. Money is a whole number of US dollars, and any object that carries fares also carries "currency": "USD". Every documented key is always present; an unknown value is null, never absent.

Pagination

List endpoints take page (from 1) and per_page (1 to 200, default 50). Read meta.has_more to know whether to ask for the next page; meta.total is the full count.

Errors

Errors use the same HTTP status as the body and are never cached.

{
    "error": {
        "status": 403,
        "code": "plan_required",
        "message": "This endpoint needs the Business plan.",
        "required_plan": "business"
    }
}
StatusCodeMeaning
400bad_requestA parameter is missing, malformed or out of range. The message names it.
401invalid_keyThe key is malformed, unknown or revoked. We never fall back to anonymous access on a bad key.
403account_suspendedThe account is suspended.
403plan_requiredYour plan does not include this endpoint. The body carries required_plan.
404not_foundNo such record, or no route.
405method_not_allowedThe path exists but not for this method.
429rate_limitedToo many calls this minute. Wait for Retry-After seconds.
429quota_exceededThe monthly cap for your plan is spent.
500internalOur fault. Safe to retry; if it persists, write to us.

Limits and caching

PlanCalls a minuteCalls a month
No key30n/a
Free601,000
Developer6010,000
Business300no cap
Platform600no cap
Enterprise600no cap

Minute windows are fixed, not sliding. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; plans with a monthly cap also carry X-Quota-Limit and X-Quota-Remaining. A 2xx response to a keyed request counts toward the month. Errors and 304s do not.

Open endpoints answered to anonymous requests are cacheable for an hour and carry an ETag, so send If-None-Match and take the 304. Everything keyed or gated is private, no-store. The data changes weekly; polling more often than the Monday load buys nothing.

Webhooks

Business plans and above can have the change feed pushed to an HTTPS endpoint. Add one on the webhooks page, with optional ship, line and port filters. After each weekly load we send one delivery per endpoint, paged at 1,000 events.

Delivery

POST https://your.app/hooks/cruise
Content-Type: application/json
X-CI-Signature: t=1790900000,v1=9f2c...e1

{
    "id": 4812,
    "type": "changes.batch",
    "detected_on": "2026-09-28",
    "previous_on": "2026-09-21",
    "page": 1,
    "pages": 3,
    "events": [
        {
            "id": 90112,
            "kind": "price_drop",
            "...": "..."
        }
    ]
}

The signature is an HMAC-SHA256, hex encoded, of "<t>.<raw body>" using your endpoint's secret. Verify it against the raw bytes before parsing, and reject timestamps more than five minutes old.

Verification, Node

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const mac = createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex');
  const a = Buffer.from(mac), b = Buffer.from(parts.v1 ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Reply with any 2xx within 10 seconds. Anything else is retried after 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, then 24 hours again, for 8 attempts in all, and then marked failed. Twenty failed deliveries in a row disable the endpoint. We only deliver to public HTTPS addresses.

Reading the data

  • Fares are weekly snapshots. They are not live bookable fares. null for a cabin means it was not offered or was sold out when we looked; the source does not distinguish.
  • Itinerary days are partly estimated. The embark and disembark days are exact. Days between carry day_confidence: exact when the stops fill every day, estimated otherwise.
  • Slugs are the public identifiers. Lines, ships, ports, destinations, regions and cruises use slugs. Sailings use their integer id. Port ids that were merged in the past resolve to the surviving port.
  • The load runs Mondays. meta.as_of is the date of the last one.

Service

GET/v1No key

Service root

Name, version and links. Useful as a connectivity check; it needs no key.

Example

curl 'https://cruise-itinerary.com/v1'

Response 200

{
    "data": {
        "name": "Cruise Itinerary Data API",
        "version": "1",
        "docs": "https://cruise-itinerary.com/docs",
        "openapi": "https://cruise-itinerary.com/openapi.json",
        "as_of": "2026-09-28"
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/statusNo key

Data status

How fresh the data is and how much of it there is. Inventory is reloaded once a week; next_scheduled_load says when.

Example

curl 'https://cruise-itinerary.com/v1/status'

Response 200

{
    "data": {
        "as_of": "2026-09-28",
        "counts": {
            "sailings": 61010,
            "ships": 517,
            "ports_with_calls": 2790,
            "lines": 43
        },
        "next_scheduled_load": "2026-10-05T03:20:00-07:00"
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Reference data

GET/v1/linesNo key

List cruise lines

Every cruise line, alphabetical, with how many ships and upcoming sailings each has.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.

Example

curl 'https://cruise-itinerary.com/v1/lines'

Response 200

{
    "data": [
        {
            "id": "amawaterways",
            "name": "AmaWaterways",
            "ship_count": 38,
            "sailing_count": 3199
        },
        {
            "id": "american-cruise-lines",
            "name": "American Cruise Lines",
            "ship_count": 23,
            "sailing_count": 696
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 45,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/lines/{id}No key

Get a cruise line

One line by slug.

Parameters

NameInTypeNotes
idrequiredpathstringLine slug.

Example

curl 'https://cruise-itinerary.com/v1/lines/holland-america-line'

Response 200

{
    "data": {
        "id": "holland-america-line",
        "name": "Holland America Line",
        "ship_count": 13,
        "sailing_count": 1234
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/shipsNo key

List ships

Ships, alphabetical. q matches part of the name or an exact IMO number.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
linequerystringOnly ships of this line (slug).
qquerystringName contains, or exact IMO.

Example

curl 'https://cruise-itinerary.com/v1/ships'

Response 200

{
    "data": [
        {
            "id": "eurodam",
            "name": "Eurodam",
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "imo": "9378448",
            "mmsi": "245206000",
            "passengers": 2104,
            "crew": 929,
            "gross_tonnage": 86273,
            "decks": 11,
            "length_ft": 936,
            "width_ft": 106,
            "maiden_voyage": "2008-07-05",
            "launched": "2008-01-01",
            "refurbished": null,
            "adults_only": false
        },
        {
            "id": "koningsdam",
            "name": "Koningsdam",
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "imo": "9692557",
            "mmsi": "244830547",
            "passengers": 2650,
            "crew": 1025,
            "gross_tonnage": 99500,
            "decks": 12,
            "length_ft": 935,
            "width_ft": 115,
            "maiden_voyage": "2016-04-04",
            "launched": null,
            "refurbished": null,
            "adults_only": false
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 13,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/ships/{id}No key

Get a ship

One ship by slug.

Parameters

NameInTypeNotes
idrequiredpathstringShip slug.

Example

curl 'https://cruise-itinerary.com/v1/ships/eurodam'

Response 200

{
    "data": {
        "id": "eurodam",
        "name": "Eurodam",
        "line": {
            "id": "holland-america-line",
            "name": "Holland America Line"
        },
        "imo": "9378448",
        "mmsi": "245206000",
        "passengers": 2104,
        "crew": 929,
        "gross_tonnage": 86273,
        "decks": 11,
        "length_ft": 936,
        "width_ft": 106,
        "maiden_voyage": "2008-07-05",
        "launched": "2008-01-01",
        "refurbished": null,
        "adults_only": false
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/portsNo key

List ports

Ports and scenic stops. Use has_calls=true for ports a ship is scheduled to visit. For matching free text to a port, use /v1/resolve.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
qquerystringName or alias contains.
destinationquerystringDestination slug.
regionquerystringRegion slug.
has_callsquerystringtrue: only ports with scheduled calls; false: only those without. One of: true, false.
kindquerystringport or scenic. One of: port, scenic.

Example

curl 'https://cruise-itinerary.com/v1/ports'

Response 200

{
    "data": [
        {
            "id": "miami",
            "name": "Miami",
            "kind": "port",
            "latitude": 25.7890972,
            "longitude": -80.2040435,
            "destination": {
                "id": "miami",
                "name": "Miami"
            },
            "region": {
                "id": "united-states",
                "name": "United States"
            },
            "aliases": [],
            "upcoming_calls": 2424
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 1,
        "has_more": false
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/ports/{id}No key

Get a port

One port by slug. The numeric id of a port that was merged into another also works and returns the surviving port, with meta.resolved_from set to the id you sent.

Parameters

NameInTypeNotes
idrequiredpathstringPort slug, or the numeric id of a merged legacy port.

Example

curl 'https://cruise-itinerary.com/v1/ports/miami'

Response 200

{
    "data": {
        "id": "miami",
        "name": "Miami",
        "kind": "port",
        "latitude": 25.7890972,
        "longitude": -80.2040435,
        "destination": {
            "id": "miami",
            "name": "Miami"
        },
        "region": {
            "id": "united-states",
            "name": "United States"
        },
        "aliases": [],
        "upcoming_calls": 2424
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/destinationsNo key

List destinations

Destinations with their region and a representative coordinate.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.

Example

curl 'https://cruise-itinerary.com/v1/destinations'

Response 200

{
    "data": [
        {
            "id": "abu-dhabi",
            "name": "Abu Dhabi",
            "region": {
                "id": "middle-east",
                "name": "Middle East"
            },
            "latitude": 24.453884,
            "longitude": 54.3773438
        },
        {
            "id": "acapulco",
            "name": "Acapulco",
            "region": {
                "id": "mexico-region",
                "name": "Mexico region"
            },
            "latitude": 16.863611,
            "longitude": -99.8825
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 379,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/regionsNo key

List regions

The 18 top-level regions.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.

Example

curl 'https://cruise-itinerary.com/v1/regions'

Response 200

{
    "data": [
        {
            "id": "africa",
            "name": "Africa"
        },
        {
            "id": "alaska-region",
            "name": "Alaska region"
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 18,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Resolver

GET/v1/resolveNo key

Resolve a name

Match free text such as "Port Canaveral, Florida" or "Royal Caribbean Symphony of the Seas" to ports, ships and lines. Returns up to five candidates, best first. Confidence is 1.0 for an exact name, about 0.9 to 0.97 after normalising accents, punctuation, country suffixes and aliases, and 0.85 or less for approximate matches. For many names at once, use the POST form.

Parameters

NameInTypeNotes
qrequiredquerystringThe text to match, up to 200 characters.
typequerystringRestrict to one kind of entity. One of: port, ship, line.

Example

curl 'https://cruise-itinerary.com/v1/resolve?q=Port%20Canaveral%2C%20Florida'

Response 200

{
    "data": [
        {
            "type": "port",
            "id": "port-canaveral",
            "name": "Port Canaveral",
            "confidence": 0.9,
            "match": "normalized"
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 1,
        "total": 1,
        "has_more": false
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

POST/v1/resolveFree

Resolve names in bulk

Up to 100 items per request. The response holds one entry per item, in order, each with its own candidate list.

Needs a key on the Free plan or above. Lower plans receive 403 plan_required.

Request body

{
    "items": [
        {
            "q": "Port Canaveral",
            "type": "port"
        },
        {
            "q": "Allure of the seas"
        }
    ]
}

Example

curl -X POST 'https://cruise-itinerary.com/v1/resolve' \
  -H "Authorization: Bearer $CI_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"q":"Port Canaveral","type":"port"},{"q":"Allure of the seas"}]}'

Response 200

{
    "data": [
        {
            "q": "Port Canaveral",
            "type": "port",
            "candidates": [
                {
                    "type": "port",
                    "id": "port-canaveral",
                    "name": "Port Canaveral",
                    "confidence": 1,
                    "match": "exact"
                }
            ]
        },
        {
            "q": "Allure of the seas",
            "type": null,
            "candidates": [
                {
                    "type": "ship",
                    "id": "allure-of-the-seas",
                    "name": "Allure of the Seas",
                    "confidence": 1,
                    "match": "exact"
                }
            ]
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 2,
        "has_more": false
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Sailings

GET/v1/sailingsDeveloper

Search sailings

Upcoming sailings with their latest fares. Fares are weekly snapshots from the inventory feed, not live bookable prices. port matches sailings that call at a port (itinerary days), embark_port the departure port. from and to bound the departure date.

Needs a key on the Developer plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
shipquerystringShip slug.
linequerystringLine slug.
portquerystringPort slug; sailings that call there.
embark_portquerystringDeparture port slug.
regionquerystringRegion slug.
fromquerystring (date)Earliest departure date.
toquerystring (date)Latest departure date.
min_nightsqueryintegerMinimum length.
max_nightsqueryintegerMaximum length.
cabinquerystringRestrict to sailings with a fare in this cabin; also the cabin max_fare and sort=fare use. One of: inside, outside, balcony, suite.
max_farequeryintegerHighest fare in USD (in cabin, or in any cabin when cabin is absent).
sortquerystringdeparture (default) or fare, lowest first. One of: departure, fare.

Example

curl 'https://cruise-itinerary.com/v1/sailings' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "id": 14026605,
            "cruise": {
                "id": "1-night-pacific-northwest-eurodam-da8a2f",
                "title": "1 Night Pacific Northwest"
            },
            "ship": {
                "id": "eurodam",
                "name": "Eurodam"
            },
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "departure_date": "2026-10-03",
            "return_date": "2026-10-04",
            "nights": 1,
            "embark_port": {
                "id": "seattle",
                "name": "Seattle"
            },
            "disembark_port": {
                "id": "vancouver",
                "name": "Vancouver"
            },
            "fares": {
                "inside": null,
                "outside": 129,
                "balcony": 129,
                "suite": 239
            },
            "currency": "USD",
            "fares_changed_on": "2026-09-14",
            "observed_on": "2026-09-28"
        },
        {
            "id": 14324436,
            "cruise": {
                "id": "21-night-panama-canal-eurodam-19a3f5",
                "title": "21 Night Panama Canal"
            },
            "ship": {
                "id": "eurodam",
                "name": "Eurodam"
            },
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "departure_date": "2026-10-03",
            "return_date": "2026-10-24",
            "nights": 21,
            "embark_port": {
                "id": "seattle",
                "name": "Seattle"
            },
            "disembark_port": {
                "id": "ft-lauderdale",
                "name": "Ft. Lauderdale"
            },
            "fares": {
                "inside": null,
                "outside": null,
                "balcony": null,
                "suite": null
            },
            "currency": "USD",
            "fares_changed_on": "2026-09-07",
            "observed_on": "2026-09-28"
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 113,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/sailings/{id}Developer

Get a sailing

One sailing with its day-by-day itinerary. Embark and disembark days are exact; days in between are estimated and labelled so.

Needs a key on the Developer plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringSailing id.

Example

curl 'https://cruise-itinerary.com/v1/sailings/14026605' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": {
        "id": 14026605,
        "cruise": {
            "id": "1-night-pacific-northwest-eurodam-da8a2f",
            "title": "1 Night Pacific Northwest"
        },
        "ship": {
            "id": "eurodam",
            "name": "Eurodam"
        },
        "line": {
            "id": "holland-america-line",
            "name": "Holland America Line"
        },
        "departure_date": "2026-10-03",
        "return_date": "2026-10-04",
        "nights": 1,
        "embark_port": {
            "id": "seattle",
            "name": "Seattle"
        },
        "disembark_port": {
            "id": "vancouver",
            "name": "Vancouver"
        },
        "fares": {
            "inside": null,
            "outside": 129,
            "balcony": 129,
            "suite": 239
        },
        "currency": "USD",
        "fares_changed_on": "2026-09-14",
        "observed_on": "2026-09-28",
        "itinerary": [
            {
                "day_offset": 0,
                "date": "2026-10-03",
                "port": {
                    "id": "seattle",
                    "name": "Seattle",
                    "latitude": 47.6062095,
                    "longitude": -122.3320708
                },
                "kind": "embark",
                "day_confidence": "exact"
            },
            {
                "day_offset": 1,
                "date": "2026-10-04",
                "port": {
                    "id": "vancouver",
                    "name": "Vancouver",
                    "latitude": 49.261226,
                    "longitude": -123.1139268
                },
                "kind": "disembark",
                "day_confidence": "exact"
            }
        ]
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/sailings/{id}/pricesBusiness

Fare history of a sailing

Every weekly snapshot of the sailing's four fares, oldest first. History starts on 2026-09-06 and continues after the sailing departs.

Needs a key on the Business plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringSailing id.
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.

Example

curl 'https://cruise-itinerary.com/v1/sailings/14026605/prices' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "observed_on": "2026-09-06",
            "fares": {
                "inside": 129,
                "outside": 129,
                "balcony": 129,
                "suite": 239
            },
            "currency": "USD"
        },
        {
            "observed_on": "2026-09-07",
            "fares": {
                "inside": 129,
                "outside": 129,
                "balcony": 129,
                "suite": 239
            },
            "currency": "USD"
        },
        {
            "observed_on": "2026-09-14",
            "fares": {
                "inside": null,
                "outside": 129,
                "balcony": 129,
                "suite": 239
            },
            "currency": "USD"
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 3,
        "total": 5,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/cruises/{id}Developer

Get a cruise

A cruise is the product (title, ship, ordered stops); a sailing is one departure of it. Cruise slugs are permanent.

Needs a key on the Developer plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringCruise slug.

Example

curl 'https://cruise-itinerary.com/v1/cruises/1-night-pacific-northwest-eurodam-da8a2f' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": {
        "id": "1-night-pacific-northwest-eurodam-da8a2f",
        "title": "1 Night Pacific Northwest",
        "ship": {
            "id": "eurodam",
            "name": "Eurodam"
        },
        "line": {
            "id": "holland-america-line",
            "name": "Holland America Line"
        },
        "nights": 1,
        "embark_port": {
            "id": "seattle",
            "name": "Seattle"
        },
        "disembark_port": {
            "id": "vancouver",
            "name": "Vancouver"
        },
        "stops": [
            {
                "seq": 1,
                "port": {
                    "id": "seattle",
                    "name": "Seattle"
                }
            },
            {
                "seq": 2,
                "port": {
                    "id": "vancouver",
                    "name": "Vancouver"
                }
            }
        ],
        "next_departure": "2026-10-03",
        "sailing_count": 2
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Ships and ports

GET/v1/ships/{id}/scheduleDeveloper

Ship schedule

Where a ship is on each day: one row per stop, with sea days. When sailings overlap, the shortest one that covers a day owns it. Window limit: 400 days; default 90 days from today.

Needs a key on the Developer plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringShip slug.
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
fromquerystring (date)First day (default today).
toquerystring (date)Last day.

Example

curl 'https://cruise-itinerary.com/v1/ships/eurodam/schedule' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "date": "2026-10-03",
            "seq": 0,
            "kind": "embark",
            "port": {
                "id": "seattle",
                "name": "Seattle",
                "latitude": 47.6062095,
                "longitude": -122.3320708
            },
            "day_confidence": "exact",
            "sailing_id": 14026605
        },
        {
            "date": "2026-10-04",
            "seq": 0,
            "kind": "turnaround",
            "port": {
                "id": "vancouver",
                "name": "Vancouver",
                "latitude": 49.261226,
                "longitude": -123.1139268
            },
            "day_confidence": "exact",
            "sailing_id": 14317445
        },
        {
            "date": "2026-10-05",
            "seq": 0,
            "kind": "sea",
            "port": null,
            "day_confidence": "estimated",
            "sailing_id": 14317445
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 3,
        "total": 29,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/ports/{id}/calendarBusiness

Port traffic calendar

Ships, lower berths and turnarounds per day. Only days with at least one ship appear; a missing day has none. Window limit: 730 days; default 90 days from today. Berths count lower-berth capacity; real headcount runs 10 to 20 percent higher.

Needs a key on the Business plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringPort slug (or merged legacy id).
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
fromquerystring (date)First day (default today).
toquerystring (date)Last day.

Example

curl 'https://cruise-itinerary.com/v1/ports/miami/calendar' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "date": "2026-11-01",
            "ships": 7,
            "berths": 26885,
            "ships_no_capacity": 0,
            "embark_ships": 6,
            "disembark_ships": 7,
            "turnaround_ships": 6,
            "estimated_ships": 0,
            "busy_pct": 90
        },
        {
            "date": "2026-11-02",
            "ships": 3,
            "berths": 12560,
            "ships_no_capacity": 0,
            "embark_ships": 3,
            "disembark_ships": 3,
            "turnaround_ships": 3,
            "estimated_ships": 0,
            "busy_pct": 39
        },
        {
            "date": "2026-11-05",
            "ships": 4,
            "berths": 11456,
            "ships_no_capacity": 0,
            "embark_ships": 4,
            "disembark_ships": 4,
            "turnaround_ships": 4,
            "estimated_ships": 0,
            "busy_pct": 30
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 3,
        "total": 27,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/ports/{id}/days/{date}Business

Port day detail

The counts for one day plus the ships behind them. A day with no ships returns zero counts and an empty calls list.

Needs a key on the Business plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
idrequiredpathstringPort slug (or merged legacy id).
daterequiredpathstringYYYY-MM-DD.

Example

curl 'https://cruise-itinerary.com/v1/ports/miami/days/2026-11-14' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": {
        "date": "2026-11-14",
        "ships": 7,
        "berths": 23928,
        "ships_no_capacity": 1,
        "embark_ships": 7,
        "disembark_ships": 7,
        "turnaround_ships": 7,
        "estimated_ships": 0,
        "busy_pct": 85,
        "calls": [
            {
                "ship": {
                    "id": "carnival-magic",
                    "name": "Carnival Magic",
                    "passengers": 3690
                },
                "line": {
                    "id": "carnival-cruise-line",
                    "name": "Carnival Cruise Line"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14252373
            },
            {
                "ship": {
                    "id": "carnival-sunrise",
                    "name": "Carnival Sunrise",
                    "passengers": 2984
                },
                "line": {
                    "id": "carnival-cruise-line",
                    "name": "Carnival Cruise Line"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14252960
            },
            {
                "ship": {
                    "id": "msc-world-america",
                    "name": "MSC World America",
                    "passengers": 5240
                },
                "line": {
                    "id": "msc-cruises",
                    "name": "MSC Cruises"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14290891
            },
            {
                "ship": {
                    "id": "norwegian-luna",
                    "name": "Norwegian Luna",
                    "passengers": null
                },
                "line": {
                    "id": "norwegian-cruise-line",
                    "name": "Norwegian Cruise Line"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14221900
            },
            {
                "ship": {
                    "id": "freedom-of-the-seas",
                    "name": "Freedom of the Seas",
                    "passengers": 3634
                },
                "line": {
                    "id": "royal-caribbean",
                    "name": "Royal Caribbean"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14240653
            },
            {
                "ship": {
                    "id": "icon-of-the-seas",
                    "name": "Icon of the Seas",
                    "passengers": 5610
                },
                "line": {
                    "id": "royal-caribbean",
                    "name": "Royal Caribbean"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14089008
            },
            {
                "ship": {
                    "id": "scarlet-lady",
                    "name": "Scarlet Lady",
                    "passengers": 2770
                },
                "line": {
                    "id": "virgin-voyages",
                    "name": "Virgin Voyages"
                },
                "kind": "turnaround",
                "day_confidence": "exact",
                "sailing_id": 14199956
            }
        ]
    },
    "meta": {
        "as_of": "2026-09-28"
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Changes

GET/v1/changesBusiness

Change feed

What changed between consecutive weekly loads: fare moves per cabin, cabins appearing or vanishing, sailings added or removed, itineraries swapped. Newest load first. kind takes a comma-separated list.

Needs a key on the Business plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
sincequerystring (date)Earliest detected_on.
untilquerystring (date)Latest detected_on.
kindquerystringComma-separated: price_drop, price_rise, cabin_unavailable, cabin_available, sailing_added, sailing_removed, itinerary_changed.
shipquerystringShip slug.
linequerystringLine slug.
portquerystringPort slug; sailings that call there.

Example

curl 'https://cruise-itinerary.com/v1/changes' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "id": 254254,
            "detected_on": "2026-09-28",
            "previous_on": "2026-09-21",
            "kind": "price_drop",
            "sailing_id": 14038925,
            "cruise_id": "7-night-eastern-caribbean-holiday-amber-cove-and-bahamas-nieuw-amsterdam-ecb222",
            "previous_cruise_id": null,
            "ship": {
                "id": "nieuw-amsterdam",
                "name": "Nieuw Amsterdam"
            },
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "departure_date": "2026-12-20",
            "cabin": "inside",
            "old_value": 749,
            "new_value": 699,
            "currency": "USD"
        },
        {
            "id": 254255,
            "detected_on": "2026-09-28",
            "previous_on": "2026-09-21",
            "kind": "price_drop",
            "sailing_id": 14038929,
            "cruise_id": "9-night-eastern-caribbean-st-maarten-antigua-and-bahamas-nieuw-amsterdam-44b3c2",
            "previous_cruise_id": null,
            "ship": {
                "id": "nieuw-amsterdam",
                "name": "Nieuw Amsterdam"
            },
            "line": {
                "id": "holland-america-line",
                "name": "Holland America Line"
            },
            "departure_date": "2026-12-11",
            "cabin": "inside",
            "old_value": 899,
            "new_value": 849,
            "currency": "USD"
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 2,
        "total": 869,
        "has_more": true
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Price index

GET/v1/price-index/latestNo key

Latest price index

The all-lines Cruise Price Index plus the eight largest lines, for cabin any and segment all, over the last 12 loads. Ordered all lines first, then by line name, oldest load first. The series starts on 2026-09-06 at 100.

Example

curl 'https://cruise-itinerary.com/v1/price-index/latest'

Response 200

{
    "data": [
        {
            "observed_on": "2026-09-06",
            "previous_on": null,
            "line": null,
            "cabin": "any",
            "segment": "all",
            "matched": 54299,
            "index_value": 100,
            "mean_log_change": null,
            "pct_cut": null,
            "pct_raised": null,
            "pct_unavailable": null,
            "avg_per_diem": null
        },
        {
            "observed_on": "2026-09-07",
            "previous_on": "2026-09-06",
            "line": null,
            "cabin": "any",
            "segment": "all",
            "matched": 54299,
            "index_value": 99.9036,
            "mean_log_change": -0.000964,
            "pct_cut": 0.62,
            "pct_raised": 0.52,
            "pct_unavailable": 0.18,
            "avg_per_diem": 464.59
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 45,
        "total": 45,
        "has_more": false
    }
}

Also returns: 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/price-indexBusiness

Price index series

The full series for one line, cabin and segment, oldest first. line is a slug, or omitted (or all) for all lines. Rows exist only where at least 30 sailings matched.

Needs a key on the Business plan or above. Lower plans receive 403 plan_required.

Parameters

NameInTypeNotes
pagequeryintegerPage number, from 1. Pages deeper than 100,000 rows are refused. Default: 1. Range: at least 1.
per_pagequeryintegerRows per page, 1 to 200. Default: 50. Range: 1 to 200.
linequerystringLine slug or all.
cabinquerystringCabin class (default any). One of: inside, outside, balcony, suite, any.
segmentquerystringall (default), w000_090, w091_180, w181_365, w366_plus, or a sail quarter like q2027_1.
fromquerystring (date)Earliest observed_on.
toquerystring (date)Latest observed_on.

Example

curl 'https://cruise-itinerary.com/v1/price-index' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "observed_on": "2026-09-06",
            "previous_on": null,
            "line": {
                "id": "carnival-cruise-line",
                "name": "Carnival Cruise Line"
            },
            "cabin": "balcony",
            "segment": "w000_090",
            "matched": 300,
            "index_value": 100,
            "mean_log_change": null,
            "pct_cut": null,
            "pct_raised": null,
            "pct_unavailable": null,
            "avg_per_diem": null
        },
        {
            "observed_on": "2026-09-07",
            "previous_on": "2026-09-06",
            "line": {
                "id": "carnival-cruise-line",
                "name": "Carnival Cruise Line"
            },
            "cabin": "balcony",
            "segment": "w000_090",
            "matched": 300,
            "index_value": 100.3153,
            "mean_log_change": 0.003148,
            "pct_cut": 3,
            "pct_raised": 7,
            "pct_unavailable": 2.28,
            "avg_per_diem": 150.02
        },
        {
            "observed_on": "2026-09-14",
            "previous_on": "2026-09-07",
            "line": {
                "id": "carnival-cruise-line",
                "name": "Carnival Cruise Line"
            },
            "cabin": "balcony",
            "segment": "w000_090",
            "matched": 255,
            "index_value": 100.3892,
            "mean_log_change": 0.000737,
            "pct_cut": 51.76,
            "pct_raised": 42.35,
            "pct_unavailable": 13.56,
            "avg_per_diem": 148.1
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 50,
        "total": 5,
        "has_more": false
    }
}

Also returns: 400 Bad request: an unknown or malformed parameter. The message names it.; 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

Bulk exports

GET/v1/exportsPlatform

List weekly exports

The last eight weekly bulk files, as gzipped CSV, with row counts and SHA-256 checksums. Column names match the API and ids are slugs, so files join to the identity-* files. access: anon files download without a key.

Needs a key on the Platform plan or above. Lower plans receive 403 plan_required.

Example

curl 'https://cruise-itinerary.com/v1/exports' \
  -H "Authorization: Bearer $CI_API_KEY"

Response 200

{
    "data": [
        {
            "date": "2026-09-28",
            "files": [
                {
                    "name": "identity-lines.csv.gz",
                    "bytes": 901,
                    "rows": 45,
                    "sha256": "88aff6f5f821b9a1aa20d69906c003c10a61971621965745846e9ca727974916",
                    "access": "anon",
                    "url": "https://cruise-itinerary.com/v1/exports/2026-09-28/identity-lines.csv.gz"
                },
                {
                    "name": "identity-ships.csv.gz",
                    "bytes": 20361,
                    "rows": 674,
                    "sha256": "057b373853a8b2aaee3e54dfdddd02f92e435d307aece09ba6f80aad06011ce6",
                    "access": "anon",
                    "url": "https://cruise-itinerary.com/v1/exports/2026-09-28/identity-ships.csv.gz"
                }
            ]
        }
    ],
    "meta": {
        "as_of": "2026-09-28",
        "page": 1,
        "per_page": 1,
        "total": 1,
        "has_more": false
    }
}

Also returns: 401 The key is malformed, unknown or revoked. A bad key is never treated as anonymous.; 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.

GET/v1/exports/{date}/{file}No key

Download an export file

Streams one gzipped CSV. date may be latest. Files named identity-* (lines, ships, ports, destinations, regions) need no key; the rest need Platform.

Parameters

NameInTypeNotes
daterequiredpathstringExport date from the listing, or latest.
filerequiredpathstringFile name from the listing.

Example

curl 'https://cruise-itinerary.com/v1/exports/latest/identity-lines.csv.gz'

Also returns: 403 The plan does not include this endpoint (plan_required, with required_plan), or the account is suspended (account_suspended).; 404 No such entity or endpoint.; 429 Per-minute rate limit (rate_limited, with Retry-After) or monthly quota (quota_exceeded) reached.; 500 Internal error. Safe to retry.. See errors.