Skip to main content

Quote an Option

Prebooking (POST /connect/hotels/v1/prebooking) is the quote/recheck step: it validates that a specific option is still available at the advertised price. In guides we call this quote; the HTTP path name is prebooking. Always run it immediately before booking so price and option state match what the supplier expects.

Endpoint

POST /connect/hotels/v1/prebooking

Why Quote Before Booking?

  1. Price Validation - Prices can change between search and booking
  2. Availability Confirmation - Inventory may have been sold
  3. Policy Updates - Cancellation policies may have changed
  4. Expected before book - Booking should use the latest prebooking output (optionRefId and prices), not a stale search result

Request

Request Body Structure

The request body contains criteria and settings:

ParameterTypeDescription
criteriaobjectQuote criteria (required)
settingsobjectRequest settings (required)

Quote Criteria

ParameterTypeDescription
optionRefIdstringOption reference ID from availability search (required)
additionalDataobjectAdditional parameters (optional)

Additional Data Parameters

ParameterTypeDescription
skipMarkupstringWhen "true", skip margin calculation. markupGross equals the provider gross.
paymentModestringPayment policy applied to this quote. "MERCHANT" (default) or "DIRECT_ONLY". See Payment Mode below.

Payment Mode

Some hotel rates require a Virtual Credit Card (VCC) at booking — typically non-refundable or merchant-collected rates. The aggregator surfaces this with optionQuote.acceptVCard = true.

You can control how these rates are returned via criteria.additionalData.paymentMode:

ValueBehavior
MERCHANT (default)All rates are returned. If your booking flow can collect a paymentCard, you can book any rate. Without one, rates with acceptVCard=true are rejected at book with ERR_CODE_MISSING_FIELDS and a clear, structured description.
DIRECT_ONLYThe aggregator rejects rates that require a VCC at quote time. The response contains errors[0].code = ERR_CODE_MISSING_FIELDS and optionQuote is omitted. Use this when your integration does not (yet) capture VCC details.

Resolution order (highest priority first):

  1. Per-request criteria.additionalData.paymentMode (the value in the request body).
  2. Per-connection map on the aggregator (AGGREGATOR_PAYMENT_MODE_BY_CONNECTION env, JSON map with optional * prefix patterns, e.g. {"testb-gog-*": "DIRECT_ONLY"}).
  3. Cluster-wide default (AGGREGATOR_PAYMENT_MODE_DEFAULT env, falls back to MERCHANT).

If you call the API through the agency stack (bundleport-booking-hotel), an optional BOOKING_PAYMENT_MODE env on the agency deployment can force a single mode on every prebook/book it issues. When unset (the default), the agency forwards the call as-is and lets the aggregator resolve the mode per-connection.

QA shortcut

The Connect UI at /connect/api-search/hotels recognizes a paymentMode query parameter (e.g. https://dev-app.bundleport.com/connect/api-search/hotels?paymentMode=DIRECT_ONLY). It is copied verbatim into additionalData.paymentMode for both the quote and the book call of that session, so you can validate the policy without changing the cluster or the agency env. This is debug-only — production traffic should rely on the per-connection or default resolution.

Example Request

{
"criteria": {
"optionRefId": "OPT-123456789",
"additionalData": {
"skipMarkup": "true",
"paymentMode": "DIRECT_ONLY"
}
},
"settings": {
"connectionCodes": ["testb-hbds-1876"],
"requestId": "quote-001"
}
}

Response

Success Response

{
"optionQuote": {
"optionRefId": "OPT-123456789",
"hotel": {
"code": "12345",
"name": "Example Hotel Barcelona"
},
"rooms": [
{
"description": "Standard Double Room",
"boardCode": "BB",
"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",
"binding": true
}
}
],
"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",
"binding": true
},
"cancelPolicy": {
"refundable": true,
"cancelPenalties": []
},
"warnings": []
},
"errors": []
}

Price Changed Response

If the price has changed:

{
"optionQuote": {
"optionRefId": "OPT-123456789",
"price": {
"currency": "EUR",
"net": 165.00, // Price increased from 150.00
"suggested": 165.00,
"gross": 165.00,
"markupGross": 198.00,
"markupNet": 198.00,
"markupCurrency": "EUR",
"markupBinding": true,
"marginAmount": 33.00,
"marginPercent": 20.0,
"marginType": "PERCENTAGE",
"binding": true
}
},
"warnings": [
{
"code": "PRICE_CHANGED",
"description": "Price has changed since search"
}
]
}

Option No Longer Available

If the option is no longer available:

{
"errors": [
{
"code": "OPTION_EXPIRED",
"message": "Option is no longer available",
"type": "CLIENT"
}
]
}

Key Fields

binding Price

When price.binding is true, the price is guaranteed and will not change before booking. When false, the price may still change.

Price Changes

If the price changes:

  1. Check warnings for PRICE_CHANGED
  2. Compare price.net with your stored value
  3. Present updated price to user
  4. Book with new price if acceptable

No Markup Warning

If a connection has no markup configured, you'll receive a warning:

{
"warnings": [
{
"code": "WARN_CODE_NONE",
"description": "No markup configured for connection; selling price uses provider gross/suggested",
"connectionCode": "testb-hbds-1876",
"additionalData": {
"warning_type": "NO_MARKUP_CONFIGURED",
"connection_code": "testb-hbds-1876"
}
}
]
}

This is informational only—the quote is still valid, but markupGross equals the provider gross (no org margin added).

Option Expiration

If optionRefId has expired:

  1. Perform a new search
  2. Select a new option
  3. Quote the new option
  4. Book immediately

Best Practices

These flows mirror what you should implement in production: quote refreshes price and locks policy context; book must run on that fresh quote. The samples show control flow—rename helpers to match your HTTP client layer.

1. Always quote before booking

// ✅ Good - Quote then book
const search = await searchHotels(criteria);
const option = search.options[0];

// Quote immediately
const quote = await quoteOption(option.optionRefId);

// Check for price changes
if (quote.warnings.some(w => w.code === 'PRICE_CHANGED')) {
// Show updated price to user
showPriceUpdate(quote.optionQuote.price);
}

// Book with quoted option
const booking = await bookOption(option.optionRefId, travellerData);

// ❌ Bad - Book without quoting
const search = await searchHotels(criteria);
const option = search.options[0];
// Price may have changed or option may be sold out
const booking = await bookOption(option.optionRefId, travellerData);

2. Handle price changes

If the supplier moves price between search and quote, you may see warnings or different totals. Reconfirm with the traveller before calling book.

const quote = await quoteOption(optionRefId);

// Check for price changes
const priceChanged = quote.warnings.some(w => w.code === 'PRICE_CHANGED');

if (priceChanged) {
const oldPrice = storedOption.price.net;
const newPrice = quote.optionQuote.price.net;

if (newPrice > oldPrice) {
// Ask user to confirm new price
const confirmed = await confirmPriceChange(newPrice);
if (!confirmed) {
// User declined, search again
return searchHotels(criteria);
}
}
}

// Proceed with booking
const booking = await bookOption(optionRefId, travellerData);

3. Check option availability

Treat hard errors (expired option, no availability) as a new search, not a retry of the same book path.

const quote = await quoteOption(optionRefId);

if (quote.errors.length > 0) {
const error = quote.errors[0];

if (error.code === 'OPTION_EXPIRED' || error.code === 'NO_AVAILABILITY') {
// Option no longer available
// Search again for alternatives
return searchHotels(criteria);
}

// Other error - handle appropriately
throw new Error(error.message);
}

Code Examples

curl -X POST https://api.bundleport.com/connect/hotels/v1/prebooking \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"criteria": {
"optionRefId": "OPT-123456789"
},
"settings": {
"connectionCodes": ["testb-hbds-1876"]
}
}'

Next Steps