Create Package Booking
See Booking flow for the end-to-end lifecycle.
Endpoint: /bookings/create/package/{:sessionId}
METHOD: POST
Creates a held booking for the chosen package departure within an existing package search session. Reserves the selected cabin(s) and surfaces any mandatory addons that are automatically included.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| sessionId | string (UUID) | Yes | Session returned by /search/packages. Must be a package session. |
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| user_key | string | Yes | Unique key associated with the calling agency. |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
packageStartDate |
string (date-time) | Yes | Package start timestamp. Must match results[].startDate 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 departure. |
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/package/{:sessionId}?user_key=abcdef' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer XXXXXXXX' \
--data '{
"packageStartDate": "2026-01-10T20:30:00",
"cabinMix": [
{
"code": "I2",
"guestIds": [1, 2]
}
]
}'
Response
{
"sessionId": "eb86ca32-e42e-4ad0-aed4-f523cf549857",
"market": "NO",
"currency": "NOK",
"departureDate": "2026-01-10T20:30:00",
"arrivalDate": "2026-01-16T09:00:00",
"departurePort": "BGO",
"arrivalPort": "KKN",
"shipCode": "NK",
"depositAmount": 1028.0,
"depositDueDate": "2025-11-14T00:00:00",
"finalDueDate": "2025-11-26T00: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"
},
{
"id": 2,
"type": "ADULT"
}
],
"cabins": [
{
"id": 1,
"code": "I2",
"cabinType": "Inside",
"cabinNumber": null,
"guests": [1, 2]
}
],
"addons": [
{
"code": "WIFI",
"title": [
{
"locale": "en",
"value": "Onboard Wi-Fi"
},
{
"locale": "nb-NO",
"value": "Wi-Fi om bord"
}
],
"category": "Other",
"guests": [1, 2],
"isMandatory": true
}
],
"excursions": [],
"supplementalTours": [],
"packageCode": "ABCDEF",
"packageStartDate": "2026-01-08T00:00:00",
"packageEndDate": "2026-01-16T00:00:00",
"packageDeparturePort": "OSL",
"packageArrivalPort": "KKN",
"priceBreakdown": {
"totalPrice": 5140.0,
"commissionTotal": 514.0,
"taxTotal": 550.71,
"cabins": [
{
"code": "I2",
"guests": [
{
"guestId": 1,
"price": 2500.0
},
{
"guestId": 2,
"price": 2500.0
}
]
}
],
"addons": [
{
"code": "WIFI",
"guests": [
{
"guestId": 1,
"price": 70
},
{
"guestId": 2,
"price": 70
}
]
}
],
"excursions": [],
"supplementalTours": []
}
}
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. |
packageCode |
string | Package code (see /packages). |
packageStartDate |
string (date-time) | Package start timestamp. |
packageEndDate |
string (date-time) | Package end timestamp. |
packageDeparturePort |
string | Package start port code. |
packageArrivalPort |
string | Package end port code. |
departureDate |
string (date-time) | Underlying voyage departure timestamp. May differ from packageStartDate when the package's start does not coincide with the voyage's departure. |
arrivalDate |
string (date-time) | Underlying voyage arrival timestamp. |
departurePort |
string | Underlying voyage departure port code. |
arrivalPort |
string | Underlying voyage 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 cruise departurePort (not packageDeparturePort): [{ locale, value }]. null when none applies. |
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. |
addons |
array of objects | Addons currently on the booking. Mandatory addons (and addons that are part of the package) 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 or carried by the package; 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/package/{:sessionId}/excursions). |
excursions[].startDate |
string (date-time) | Excursion start timestamp. |
excursions[].endDate |
string (date-time) | Excursion end timestamp. |
excursions[].isMandatory |
boolean | true for excursions that are part of the package; these cannot be removed. |
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. |
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 departure, 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. packageStartDatemust be in the future and must match a package start date 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".
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.