Create Voyage Booking
See Booking flow for the end-to-end lifecycle.
Endpoint: /bookings/create/voyage-only/{:sessionId}
METHOD: POST
Creates a held booking for the chosen voyage within an existing voyage-only search session. Reserves the selected cabin(s) and any vehicle slots requested at search time, and surfaces any mandatory addons that are automatically included.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| sessionId | string (UUID) | Yes | Session returned by /search/voyages. Must be voyage-only. |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| user_key | string | Yes | Unique key associated with the calling agency. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
departureDate |
string (date-time) | Yes | Voyage departure timestamp. Must match results[].departureDate from the originating search and be in the future. |
cabinMix |
array of objects | Yes | One entry per cabin to reserve. Total guestIds across cabins must not exceed 9, must all be unique, and must each appear as a guest in the search. |
cabinMix[].code |
string | Yes | Cabin grade code from the search response. Must be available on the selected voyage. |
cabinMix[].guestIds |
array of numbers | Yes | Passenger IDs (from the search) assigned to this cabin. Must contain at least one passenger of type: "ADULT" or type: "SENIOR" and not exceed the grade's maximumOccupancy. |
Example
Request
curl --location '{:apiBase}/bookings/create/voyage-only/{:sessionId}?user_key=abcdef' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer XXXXXXXX' \
--data '{
"departureDate": "2026-05-11T18:15:00",
"cabinMix": [
{
"code": "DECK",
"guestIds": [1]
}
]
}'
Response
{
"vehicles": [
{
"type": "CAR",
"guestId": 1
}
],
"addons": [
{
"code": "WIFI",
"title": [
{
"locale": "en",
"value": "Onboard Wi-Fi"
},
{
"locale": "nb-NO",
"value": "Wi-Fi om bord"
}
],
"category": "Other",
"guests": [1],
"isMandatory": true
}
],
"excursions": [],
"supplementalTours": [],
"priceBreakdown": {
"totalPrice": 249.0,
"commissionTotal": 24.9,
"taxTotal": 26.68,
"cabins": [
{
"code": "DECK",
"guests": [
{
"guestId": 1,
"price": 106.0
}
]
}
],
"addons": [
{
"code": "WIFI",
"guests": [
{
"guestId": 1,
"price": 70
}
]
}
],
"excursions": [],
"supplementalTours": [],
"vehicles": [
{
"code": "CAR",
"guest": {
"guestId": 1,
"price": 73.0
}
}
]
},
"sessionId": "f91c02b2-f22a-42c9-80b7-e3ef591ef0e0",
"market": "US",
"currency": "USD",
"departureDate": "2026-05-11T18:15:00",
"arrivalDate": "2026-05-12T10:55:00",
"departurePort": "TOS",
"arrivalPort": "HVG",
"shipCode": "KH",
"depositAmount": 49.8,
"depositDueDate": "2026-03-20T00:00:00",
"finalDueDate": "2026-04-11T00:00:00",
"embarkationNotification": [
{
"locale": "en",
"value": "Boarding opens 60 minutes before departure. Please be at the terminal in good time."
},
{
"locale": "nb-NO",
"value": "Ombordstigning åpner 60 minutter før avgang. Vær ved terminalen i god tid."
}
],
"guests": [
{
"id": 1,
"type": "ADULT"
}
],
"cabins": [
{
"id": 1,
"code": "DECK",
"cabinType": "Inside",
"cabinNumber": null,
"guests": [
1
]
}
]
}
Fields
| Field | Type | Description |
|---|---|---|
sessionId |
string (UUID) | The booking session this held booking belongs to. |
market |
string | Market the booking was created under. |
currency |
string | Currency the booking is priced in. |
departureDate |
string (date-time) | Voyage departure timestamp. |
arrivalDate |
string (date-time) | Voyage arrival timestamp. |
departurePort |
string | Departure port code. |
arrivalPort |
string | Arrival port code. |
shipCode |
string | Ship code (see /refdata/ships). |
depositAmount |
number | Deposit required under the agency's payment terms, in the booking currency. Recalculated with the price on every change to the held booking. Can be 0, for example when the full balance is already due. null when no payment terms apply. |
depositDueDate |
string (date-time) | Date by which the deposit is due. null when no payment terms apply. |
finalDueDate |
string (date-time) | Date by which the full balance is due. null when no payment terms apply. |
embarkationNotification |
array of objects | Localized embarkation instruction for the departure port: [{ locale, value }]. null when none applies to this departure. |
guests |
array of objects | Passengers on the booking. |
guests[].id |
number | Passenger ID (matches the IDs used in cabinMix[].guestIds). |
guests[].type |
string | One of ADULT, CHILD, INFANT, SENIOR. |
cabins |
array of objects | Cabins reserved. |
cabins[].id |
number | Identifies this cabin on the booking. Send it as cabinId when choosing a cabin number. |
cabins[].code |
string | Cabin grade code. |
cabins[].cabinType |
string | Cabin type (e.g. Inside, Outside, Suite). Categorization above the per-ship cabin grade. |
cabins[].cabinNumber |
string | The specific cabin allocated, once one has been chosen via /cabin-numbers. null while the booking holds a guarantee cabin, where the cabin is assigned before departure. |
cabins[].guests |
array of numbers | Passenger IDs assigned to this cabin. |
vehicles |
array of objects | Vehicle slots reserved. Each vehicle is bound to a passenger; vehicles cannot be added to or removed from the booking after this point. |
vehicles[].type |
string | Vehicle category (CAR, MOTORCYCLE). |
vehicles[].guestId |
number | Passenger the vehicle is associated with. |
addons |
array of objects | Addons currently on the booking. Mandatory addons are auto-included here at create time and must not be re-applied. |
addons[].code |
string | Addon code (see /bookings/{:sessionId}/addons to discover available addons for the booking). |
addons[].title |
array of objects | Localized addon name: [{ locale, value }]. null when no name is published for this addon code. |
addons[].category |
string | Addon category, such as Meal, Onboard credit or Other. null when no category is published for this addon code. |
addons[].guests |
array of numbers | Passenger IDs the addon applies to. |
addons[].isMandatory |
boolean | true for addons auto-included by Hurtigruten; these cannot be removed. |
excursions |
array of objects | Excursions currently on the booking. Always present; empty when nothing is applied. |
excursions[].code |
string | Excursion code (see /bookings/create/voyage-only/{:sessionId}/excursions). |
excursions[].startDate |
string (date-time) | Excursion start timestamp. |
excursions[].endDate |
string (date-time) | Excursion end timestamp. |
excursions[].isMandatory |
boolean | true when the excursion is bundled and cannot be individually removed. Currently always false on voyage-only bookings — non-removable excursions surface only on package bookings. |
excursions[].guests |
array of numbers | Passenger IDs the excursion applies to. |
supplementalTours |
array of objects | Supplemental tours (pre- or post-cruise extensions) currently on the booking. Always present; empty when nothing is applied. |
supplementalTours[].code |
string | Supplemental-tour code. |
supplementalTours[].startDate |
string (date-time) | Tour start timestamp. |
supplementalTours[].endDate |
string (date-time) | Tour end timestamp. |
supplementalTours[].tourCategory |
string | One of Arrival (pre-cruise) or Departure (post-cruise). |
supplementalTours[].isMandatory |
boolean | true when the tour cannot be removed. |
supplementalTours[].guests |
array of numbers | Passenger IDs the tour applies to. |
supplementalTours[].tourStageSelectableRouteOverrides |
array of objects | Per-stage route overrides chosen for the tour. Empty when no overrides apply. |
supplementalTours[].tourStageSelectableRouteOverrides[].tourStageId |
number | Tour-stage identifier. |
supplementalTours[].tourStageSelectableRouteOverrides[].departureCode |
string | Override departure port code. |
supplementalTours[].tourStageSelectableRouteOverrides[].arrivalCode |
string | Override arrival port code. |
priceBreakdown |
object | Price detail. The component price arrays sum to priceBreakdown.totalPrice. commissionTotal and taxTotal are contained within that total, not added to it. |
priceBreakdown.totalPrice |
number | Total price for the booking in the booking currency. |
priceBreakdown.commissionTotal |
number | Agency commission included in totalPrice, in the booking currency. 0 when no commission applies to the booking. |
priceBreakdown.taxTotal |
number | Norwegian VAT included in totalPrice, in the booking currency. 0 when no VAT applies; omitted when the tax amount isn't available for the booking. |
priceBreakdown.cabins |
array of objects | Per-cabin pricing. |
priceBreakdown.cabins[].code |
string | Cabin grade code. |
priceBreakdown.cabins[].guests |
array of objects | Per-passenger price for this cabin. |
priceBreakdown.cabins[].guests[].guestId |
number | Passenger ID. |
priceBreakdown.cabins[].guests[].price |
number | Passenger's portion of the cabin price. |
priceBreakdown.addons |
array of objects | Per-addon pricing. |
priceBreakdown.addons[].code |
string | Addon code. |
priceBreakdown.addons[].guests |
array of objects | Per-passenger price for this addon. |
priceBreakdown.addons[].guests[].guestId |
number | Passenger ID. |
priceBreakdown.addons[].guests[].price |
number | Passenger's portion of the addon price. |
priceBreakdown.excursions |
array of objects | Per-excursion pricing. |
priceBreakdown.excursions[].code |
string | Excursion code. |
priceBreakdown.excursions[].guests |
array of objects | Per-passenger price for this excursion. |
priceBreakdown.excursions[].guests[].guestId |
number | Passenger ID. |
priceBreakdown.excursions[].guests[].price |
number | Passenger's portion of the excursion price. |
priceBreakdown.supplementalTours |
array of objects | Per-supplemental-tour pricing. |
priceBreakdown.supplementalTours[].code |
string | Supplemental-tour code. |
priceBreakdown.supplementalTours[].guests |
array of objects | Per-passenger price for this supplemental tour. |
priceBreakdown.supplementalTours[].guests[].guestId |
number | Passenger ID. |
priceBreakdown.supplementalTours[].guests[].price |
number | Passenger's portion of the supplemental-tour price. |
priceBreakdown.vehicles |
array of objects | Per-vehicle pricing. |
priceBreakdown.vehicles[].code |
string | Vehicle category. |
priceBreakdown.vehicles[].guest.guestId |
number | Passenger the vehicle is bound to. |
priceBreakdown.vehicles[].guest.price |
number | Vehicle price. |
Errors
See Error model for response-body shapes across status codes.
| Status | Cause |
|---|---|
| 400 | user_key query parameter is missing; the request body fails validation (rules listed below); or the request references a voyage, cabin grade, or passenger that is not in the session. |
| 404 | The session does not exist or has expired. |
| 409 | A held booking already exists in this session (each session can hold one booking at a time; start a new search for another booking); or the session does not match this endpoint's booking type. |
| 500 | The agency is not set up for bookings through the API. Contact Hurtigruten to update the agency's setup. |
Validation rules. A 400 is returned when any of these rules are not met:
cabinMixmust be non-empty.- Each cabin's
codemust be non-empty andguestIdsmust be non-empty, with everyguestId > 0. - Total
guestIdsacross all cabins must not exceed 9, and everyguestIdmust be unique across the request. departureDatemust be in the future and must match a voyage's departure timestamp returned by the originating search.- Every
guestIdand every cabincodemust come from the search response. - Each cabin's
guestIdscount must not exceed the cabin grade'smaximumOccupancy, and at least one of those passengers must be oftype: "ADULT"ortype: "SENIOR". - Vehicles requested at search time must still be available on the selected voyage in the requested quantity.
Mandatory addons. If the booking includes mandatory addons (for example, on-board Wi-Fi), they appear in addons with isMandatory: true and are already priced into priceBreakdown.totalPrice. Do not call /bookings/{:sessionId}/addons for these — re-applying a mandatory addon is not supported.