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:
- Use a unique
clientReferencefor every booking attempt (e.g. UUID). Store it before you send the request. - Reconcile with booking list — call
POST /connect/hotels/v1/bookinglistwithtypeSearch: BOOKING_LIST_CRITERIA_TYPE_REFERENCESandreferences: [{ "clientReference": "<your id>" }](see List bookings) to see if a booking was created. - 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:
| Parameter | Type | Description |
|---|---|---|
input | object | Booking input data (required) |
settings | object | Request settings (required) |
Booking Input
| Parameter | Type | Description |
|---|---|---|
optionRefId | string | Option reference ID from prebooking quote (required) |
holder | object | Booking holder (main guest) information (required) |
rooms | array | Room configurations with guest details (required) |
paymentCard | object | Payment card information (if required) |
clientReference | string | Your internal booking reference |
remarks | array | Free-text or typed remarks (e.g. late check-in); see Remarks |
priceDelta | number | Price difference tolerance |
additionalData | object | Additional parameters (optional) |
Additional Data Parameters
| Parameter | Type | Description |
|---|---|---|
skipMarkup | string | When "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.GENERALor 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:
- Show a clear pending state to the end user (e.g. “Awaiting hotel confirmation”).
- Poll booking detail — call
POST /connect/hotels/v1/bookingdetailwithbookingIDon a backoff schedule (e.g. every 30–120 seconds) until status becomesCONFIRMED,FAILED, orCANCELLED, or until a maximum wait you define in product policy. - Use webhooks when configured — if you enable webhooks, listen for
booking.confirmed/booking.failedto update your system without tight polling. - Stop polling after final status or timeout; escalate via support with
bookingIDandX-Request-ID/requestIdif 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
| Status | Description |
|---|---|
CONFIRMED | Booking confirmed by provider |
PENDING | Booking request submitted, awaiting confirmation |
ON_REQUEST | Provider needs to manually confirm |
FAILED | Booking 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 usuallytitlewhere you collect it) - Contact: put email and phone in
contactInfo(not as top-level fields onholder) 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
- JavaScript
- Python
- Java
- C#
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"]
}
}'
async function createBooking(optionRefId, travellerData) {
// Quote first
const quote = await quoteOption(optionRefId);
// Book
const response = await fetch('https://api.bundleport.com/connect/hotels/v1/booking', {
method: 'POST',
headers: {
'Authorization': 'ApiKey YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: {
optionRefId,
holder: travellerData.holder,
rooms: travellerData.rooms,
paymentCard: travellerData.paymentCard,
clientReference: generateClientReference(),
},
settings: {
connectionCodes: ['testb-hbds-1876'],
},
}),
});
const booking = await response.json();
if (booking.errors?.length > 0) {
throw new Error(booking.errors[0].message);
}
return booking.booking;
}
import requests
def create_booking(option_ref_id, traveller_data):
# Quote first
quote = quote_option(option_ref_id)
# Book
url = "https://api.bundleport.com/connect/hotels/v1/booking"
headers = {
"Authorization": "ApiKey YOUR_API_KEY",
"Content-Type": "application/json"
}
payload = {
"input": {
"optionRefId": option_ref_id,
"holder": traveller_data["holder"],
"rooms": traveller_data["rooms"],
"paymentCard": traveller_data.get("paymentCard"),
"clientReference": generate_client_reference(),
},
"settings": {
"connectionCodes": ["testb-hbds-1876"]
}
}
response = requests.post(url, json=payload, headers=headers)
booking = response.json()
if booking.get("errors"):
raise Exception(booking["errors"][0]["message"])
return booking["booking"]
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var body = """
{
"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"] }
}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.bundleport.com/connect/hotels/v1/booking"))
.header("Authorization", "ApiKey YOUR_API_KEY")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = new
{
input = new
{
optionRefId = "OPT-123456789",
holder = new { name = "John", surname = "Doe", title = "MR" },
rooms = new[] {
new {
occupancyRefId = 1,
paxes = new[] {
new { name = "John", surname = "Doe", age = 35 },
new { name = "Jane", surname = "Doe", age = 33 }
}
}
}
},
settings = new { connectionCodes = new[] { "testb-hbds-1876" } }
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("ApiKey", "YOUR_API_KEY");
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
var response = await client.PostAsync(
"https://api.bundleport.com/connect/hotels/v1/booking",
content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Next Steps
- Retrieve Booking - Get booking details
- List Bookings - Query bookings
- Cancel Booking - Cancel a reservation