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",
"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",
"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,
"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). |
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[].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. Components sum to priceBreakdown.totalPrice. |
priceBreakdown.totalPrice |
number | Total price for the booking in the booking currency. |
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. |
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.