Skip to main content

Create a Booking

The book endpoint creates a hotel reservation. Always quote the option immediately before booking to ensure accurate pricing and availability.

Timeouts, idempotency, and recovery

Booking can take longer than search or quote. Configure an adequate settings.timeout (milliseconds). The platform may enforce minimum timeouts for certain supplier connections.

If the HTTP client times out or the connection drops, you may not know whether the booking was created:

  1. Use a unique clientReference for every booking attempt (e.g. UUID). Store it before you send the request.
  2. Reconcile with booking list — call POST /connect/hotels/v1/bookinglist with typeSearch: BOOKING_LIST_CRITERIA_TYPE_REFERENCES and references: [{ "clientReference": "<your id>" }] (see List bookings) to see if a booking was created.
  3. Only retry a full book after you have confirmed there is no booking for that clientReference, unless your integration has an explicit idempotency contract for the same payload.

Send X-Request-ID (or rely on settings.requestId) on every call for end-to-end tracing with support.

Endpoint

POST /connect/hotels/v1/booking

Request

Request Body Structure

The request body contains input and settings:

ParameterTypeDescription
inputobjectBooking input data (required)
settingsobjectRequest settings (required)

Booking Input

ParameterTypeDescription
optionRefIdstringOption reference ID from prebooking quote (required)
holderobjectBooking holder (main guest) information (required)
roomsarrayRoom configurations with guest details (required)
paymentCardobjectPayment card information (if required)
clientReferencestringYour internal booking reference
remarksarrayFree-text or typed remarks (e.g. late check-in); see Remarks
priceDeltanumberPrice difference tolerance
additionalDataobjectAdditional parameters (optional)

Additional Data Parameters

ParameterTypeDescription
skipMarkupstringWhen "true", skip margin calculation. Suggested price equals net price.

Example Request

{
"input": {
"optionRefId": "OPT-123456789",
"holder": {
"name": "John",
"surname": "Doe",
"title": "MR"
},
"rooms": [
{
"occupancyRefId": 1,
"paxes": [
{
"name": "John",
"surname": "Doe",
"age": 35
},
{
"name": "Jane",
"surname": "Doe",
"age": 33
}
]
}
],
"paymentCard": {
"type": "VI",
"number": "4111111111111111",
"expire": {
"month": 12,
"year": 2027
},
"holder": {
"name": "John",
"surname": "Doe",
"contactInfo": {
"email": "john.doe@example.com",
"phone": {
"countryCode": "+34",
"number": "600123456"
}
}
}
},
"clientReference": "BOOKING-2025-001",
"remarks": [
{
"type": "GENERAL",
"value": "Late check-in requested"
}
]
},
"settings": {
"connectionCodes": ["testb-hbds-1876"],
"requestId": "book-001"
}
}

Multi-room example

Two rooms: first room with two adults and one child (age 8), second room with one adult. Match occupancyRefId to the room index from your search/quote response.

{
"input": {
"optionRefId": "OPT-MULTI-ROOM",
"holder": {
"name": "John",
"surname": "Doe",
"title": "MR"
},
"rooms": [
{
"occupancyRefId": 1,
"paxes": [
{ "name": "John", "surname": "Doe", "age": 35 },
{ "name": "Jane", "surname": "Doe", "age": 33 },
{ "name": "Alex", "surname": "Doe", "age": 8 }
]
},
{
"occupancyRefId": 2,
"paxes": [
{ "name": "Chris", "surname": "Doe", "age": 40 }
]
}
],
"clientReference": "BOOKING-MULTI-001"
},
"settings": {
"connectionCodes": ["testb-hbds-1876"],
"requestId": "book-multi-001"
}
}

Remarks (special requests)

Use remarks to pass non-structural requests to the property (e.g. late arrival, floor preference). Each item typically has:

  • type — e.g. GENERAL or a supplier-specific code when documented for your connection.
  • value — human-readable text.

Suppliers may ignore or map remarks differently; treat them as best-effort, not guaranteed. For critical requirements, confirm with your connectivity team.

Response

Success Response

{
"booking": {
"bookingID": "BK-987654321",
"status": "CONFIRMED",
"clientReference": "BOOKING-2025-001",
"hotel": {
"code": "12345",
"name": "Example Hotel Barcelona"
},
"stay": {
"checkIn": "2025-06-15",
"checkOut": "2025-06-17"
},
"rooms": [
{
"description": "Standard Double Room",
"boardCode": "BB",
"confirmationReference": "ROOM-001",
"paxes": [
{
"name": "John",
"surname": "Doe",
"age": 35
},
{
"name": "Jane",
"surname": "Doe",
"age": 33
}
]
}
],
"holder": {
"name": "John",
"surname": "Doe",
"email": "john.doe@example.com",
"phone": "+34600123456"
},
"price": {
"currency": "EUR",
"net": 150.00,
"suggested": 150.00,
"gross": 150.00,
"markupGross": 180.00,
"markupNet": 180.00,
"markupCurrency": "EUR",
"markupBinding": true,
"marginAmount": 30.00,
"marginPercent": 20.0,
"marginType": "PERCENTAGE"
},
"cancelPolicy": {
"refundable": true,
"cancelPenalties": []
},
"remarks": ["Late check-in requested"],
"createdAt": "2025-06-01T10:30:00Z",
"updatedAt": "2025-06-01T10:30:00Z"
},
"warnings": [],
"errors": []
}

On Request Response

Some bookings require provider confirmation:

{
"booking": {
"bookingID": "BK-987654321",
"status": "ON_REQUEST",
"reference": {
"bookingID": "BK-987654321"
},
...
},
"warnings": [
{
"code": "ON_REQUEST",
"description": "Booking requires provider confirmation"
}
]
}

ON_REQUEST bookings (pending confirmation)

When status is ON_REQUEST, the supplier has not yet returned a final confirmation. Your application should:

  1. Show a clear pending state to the end user (e.g. “Awaiting hotel confirmation”).
  2. Poll booking detail — call POST /connect/hotels/v1/bookingdetail with bookingID on a backoff schedule (e.g. every 30–120 seconds) until status becomes CONFIRMED, FAILED, or CANCELLED, or until a maximum wait you define in product policy.
  3. Use webhooks when configured — if you enable webhooks, listen for booking.confirmed / booking.failed to update your system without tight polling.
  4. Stop polling after final status or timeout; escalate via support with bookingID and X-Request-ID / requestId if stuck.

There is no single global SLA for how long ON_REQUEST lasts; it depends on the supplier and product type.

Key Fields

Booking Status

StatusDescription
CONFIRMEDBooking confirmed by provider
PENDINGBooking request submitted, awaiting confirmation
ON_REQUESTProvider needs to manually confirm
FAILEDBooking could not be completed

Booking References

Store these references for later operations:

  • bookingID - Bundleport booking ID (use for retrieve, cancel)
  • clientReference - Your internal reference (use for list/reconciliation after timeouts)

Holder Information

The holder is the main guest and booking contact:

  • Required: name, surname (and usually title where you collect it)
  • Contact: put email and phone in contactInfo (not as top-level fields on holder) so the payload matches the API schema
  • Used for: booking confirmation, hotel communication

Guest Information (Paxes)

For each room, provide guest details:

  • Adults: name, surname, age (optional)
  • Children: name, surname, age (required)
  • Type: "ADULT" or "CHILD"

Payment Card

Required for some providers. Include:

  • Type: Card type (VI, MC, AX, etc.)
  • Number: Card number
  • Expire: Expiration month and year
  • Holder: Cardholder name

Best Practices

Operational habits that prevent double bookings, stale prices, and stuck ON_REQUEST states. Pseudocode uses placeholder functions such as quoteOption—wire them to prebooking and booking in your stack.

1. Always quote before booking

// ✅ Good - Quote then book
const quote = await quoteOption(optionRefId);
if (quote.errors?.length > 0) {
throw new Error('Option no longer available');
}
const booking = await bookOption(optionRefId, travellerData);

// ❌ Bad - Book without quoting
const booking = await bookOption(optionRefId, travellerData);
// May fail if price changed or option expired

2. Store all references

Persist Bundleport bookingID, your clientReference, and supplier references as soon as book succeeds—they are required for support, reconciliation, and cancel.

const booking = await bookOption(optionRefId, travellerData);

// Store all references
await saveBooking({
bundleportId: booking.booking.bookingID,
clientReference: booking.booking.clientReference,
status: booking.booking.status,
});

3. Handle ON_REQUEST bookings

const booking = await bookOption(optionRefId, travellerData);

if (booking.booking.status === 'ON_REQUEST') {
// Inform user that confirmation is pending
await notifyUser({
message: 'Your booking is being confirmed. You will receive an email when confirmed.',
bookingId: booking.booking.bookingID,
});

// Set up webhook or polling to check status
await monitorBookingStatus(booking.booking.bookingID);
}

4. Validate guest information

Mismatch between search occupancies and book payloads is a common 400 source; validate before POST.

function validateGuests(rooms, occupancies) {
for (let i = 0; i < rooms.length; i++) {
const room = rooms[i];
const occupancy = occupancies[i];

// Check total guests match
const totalGuests = room.paxes.length;
const expectedTotal = occupancy.adults + (occupancy.children || 0);

if (totalGuests !== expectedTotal) {
throw new Error(`Room ${i + 1}: Expected ${expectedTotal} guests, got ${totalGuests}`);
}

// Check children ages
const children = room.paxes.filter(p => p.type === 'CHILD' || p.age < 18);
if (children.length !== (occupancy.children || 0)) {
throw new Error(`Room ${i + 1}: Children count mismatch`);
}
}
}

Code Examples

curl -X POST https://api.bundleport.com/connect/hotels/v1/booking \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": {
"optionRefId": "OPT-123456789",
"holder": {
"name": "John",
"surname": "Doe",
"title": "MR"
},
"rooms": [
{
"occupancyRefId": 1,
"paxes": [
{ "name": "John", "surname": "Doe", "age": 35 },
{ "name": "Jane", "surname": "Doe", "age": 33 }
]
}
]
},
"settings": {
"connectionCodes": ["testb-hbds-1876"]
}
}'

Next Steps