Create a Booking
The book endpoint creates a hotel reservation. Prefer quoting the option immediately before booking so price and option state match what the supplier expects. Some Hotelbeds rates allow booking from search when no RECHECK remark is present — see Quote — When you can skip.
Timeouts, idempotency, and recovery
Booking confirmation is slower than search or quote. Use these figures:
| Guidance | Value |
|---|---|
| Platform floor for book | 90000 ms — anything lower is raised to this floor |
Recommended settings.timeout on book | ≥ 92000 ms, or omit to use the connection ceiling (~2 minutes) |
| Your HTTP client / socket timeout | A few seconds above settings.timeout |
Abandoning a book early does not undo a confirmation the provider may already have accepted. If your client cuts the connection first, you can end up with a reservation and no reference on your side.
If the HTTP client times out, the connection drops, or the error includes recommended_action: verify_booking_then_restart:
- 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). - Only retry a full book after you have confirmed there is no booking for that
clientReference, and only with a new search/optionRefIdwhen the failure was allotment or “do not retry”. Follow buyer action hints when present.
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 — unique per attempt; required for timeout recovery |
remarks | array | Free-text or typed remarks (e.g. late check-in); see Remarks |
priceDelta | number | Max allowed price increase as a percentage. Buyer decision — no platform default. Omit or 0 = strict. See priceDelta. |
additionalData | object | Additional parameters (optional) |
priceDelta (price tolerance)
priceDelta is the maximum percentage increase you accept between the last known price (quote or search) and the price at confirmation.
| You send | Effect |
|---|---|
Omit or 0 | Strict matching — a one-cent increase rejects the booking. Bundleport does not inject another default. |
2 | Allow up to +2% (aligned with Hotelbeds’ own documented default when you choose that commercial posture). |
| Any other value | That percentage; capped at 999.99. |
For providers that expose an upstream tolerance tag (Hotelbeds), the value is forwarded as-is. For every provider it also drives Bundleport’s own price check. The cost of any accepted increase is borne by the buyer, not by the provider.
Additional Data Parameters
| Parameter | Type | Description |
|---|---|---|
skipMarkup | string | When "true", skip margin calculation. markupGross equals the provider gross. |
paymentMode | string | Payment policy mirror of the value used at quote. "MERCHANT" (default) or "DIRECT_ONLY". See Payment Mode at quote. The book endpoint does not enforce this field today — the policy is applied at quote/prebooking time, where DIRECT_ONLY rejects rates with acceptVCard=true before they ever reach book. Pass it for audit-log consistency and forward compatibility. If you book a rate that requires a Virtual Credit Card without supplying paymentCard, the upstream provider error is returned verbatim (no aggregator-side translation). |
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. Prefer quote before booking
// ✅ Good - Quote then book (default for most providers)
const quote = await quoteOption(optionRefId);
if (quote.errors?.length > 0) {
throw new Error('Option no longer available');
}
const booking = await bookOption(quote.optionRefId ?? optionRefId, travellerData);
// ⚠️ Only for Hotelbeds BOOKABLE rates (no RATE_TYPE=RECHECK remark)
// const booking = await bookOption(searchOptionRefId, travellerData);
See Quote — When you can skip. Set an explicit priceDelta when your policy allows a limited increase.
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