Skip to main content

Bundle Price Preview

Compute the live total for a bundle โ€” including a combo's chosen options, rental duration, and extra passengers โ€” without creating a booking. This is the calculator you call as the customer changes their selection, so you can show a running combo total and the dollar savings before checkout.

It runs the same pricing engine as Create booking, so the number you preview is exactly what you'll be charged. No payment is taken, no inventory is reserved, and nothing is written.

Endpointโ€‹

POST /v1/bundles/{id}/price-preview

Authenticationโ€‹

Requires a live or test API key with products:read scope.

Path Parametersโ€‹

ParameterTypeDescription
idintegerBundle ID

Request Bodyโ€‹

FieldTypeRequiredDescription
start_datestringYesStart date/time (ISO 8601). For a combo, this is the activity start.
end_datestringYesEnd date/time (ISO 8601). Must be on or after start_date.
quantityintegerNoNumber of bundles. Defaults to 1.
choicesobjectNo (Yes for a combo){ choice_group: bundle_item_id } โ€” the chosen option for each "pick one" group (from Get bundle โ†’ choice_groups).
durationsobjectNo{ bundle_item_id: hours } โ€” the chosen rental duration for a rental item, in hours. Fractional values mean minutes: 0.5 = 30 minutes, 2.5 = 2 hours 30 minutes. Must be one of that item's allowed_durations.
participantsobjectNo{ bundle_item_id: headcount } โ€” heads on an experience option (extra heads beyond the included one are charged the experience's per-head rate, never discounted).
variationsobjectNo{ bundle_item_id: variation_id } โ€” chosen variation for a rental item that has variations.
schedulesobjectNo{ bundle_item_id: experience_schedule_id } โ€” the slot for an experience option. Used to validate the combo's structure; not required to price it.

These are the same selection keys you send to Create booking.

Durations are in hours โ€” fractions mean minutes

Every value in durations (and every entry in an item's allowed_durations) is expressed in hours. Fractional values are minutes: 0.5 = 30 minutes, 1.25 = 1 hour 15 minutes, 2.5 = 2 hours 30 minutes. A short hourly rental can therefore be priced down to the minute.

Example Requestโ€‹

Preview the Biff Rentals "Discovery Tour" combo (bundle 88): the X3 MAX excursion option 502, paired with the Sea-Doo GTI 130 watercraft (item 503) for a 2h window, 2 people on the excursion.

curl -X POST "https://app.equipdash.com/api/v1/bundles/88/price-preview" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"start_date": "2026-07-15T09:00:00Z",
"end_date": "2026-07-15T13:00:00Z",
"quantity": 1,
"choices": { "sxs": 502, "watercraft": 503 },
"durations": { "503": 2 },
"participants": { "502": 2 },
"schedules": { "502": 3300 }
}'

Responseโ€‹

Success (200 OK)โ€‹

{
"data": {
"bundle_id": 88,
"quantity": 1,
"pricing_type": "calculated",
"original_price": 838.00,
"final_price": 778.00,
"savings": 60.00,
"security_deposit_amount": 4000.00,
"items_breakdown": [
{
"bundle_item_id": 502,
"item_type": "experience",
"product_id": null,
"experience_id": 1260,
"name": "Discovery Tour Excursion โ€” X3 MAX",
"variation_id": null,
"variation_name": null,
"quantity": 1,
"duration_hours": null,
"participants": 2,
"original_price": 538.00,
"final_price": 538.00,
"savings": 0.00
},
{
"bundle_item_id": 503,
"item_type": "product",
"product_id": 163,
"experience_id": null,
"name": "Sea-Doo GTI 130",
"variation_id": null,
"variation_name": null,
"quantity": 1,
"duration_hours": 2,
"participants": null,
"original_price": 300.00,
"final_price": 240.00,
"savings": 60.00
}
]
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-06-20T10:00:00Z"
}
}

In this example the excursion is full price ($499 base + $39 for the one extra head = $538), the watercraft gets the 2h tier (20% off $300 โ†’ $240, a $60 saving), for a combo total of $778.

Response Fieldsโ€‹

FieldTypeDescription
bundle_idintegerThe bundle that was priced.
quantityintegerNumber of bundles priced.
pricing_typestringcalculated or flat.
original_pricefloatSum of the chosen items at full price (before any combo discount).
final_pricefloatThe combo total the customer pays.
savingsfloatDollar savings (original_price โˆ’ final_price). Show this to the customer โ€” never the underlying discount percentage.
security_deposit_amountfloat|nullThe single flat security hold for the combo (ร— quantity), or null when deposits are inherited per item.
items_breakdownarrayPer-line pricing for each chosen item.
items_breakdown[].bundle_item_idintegerThe bundle item that was priced.
items_breakdown[].item_typestringproduct (rental) or experience.
items_breakdown[].namestringProduct or experience name.
items_breakdown[].quantityintegerTotal units for this line (item quantity ร— bundle quantity).
items_breakdown[].duration_hoursfloat|nullThe chosen rental duration in hours (rental items only). Fractional values are minutes (0.5 = 30 min, 2.5 = 2h 30m).
items_breakdown[].participantsinteger|nullHeads on this line (experience items only).
items_breakdown[].original_pricefloatLine full price.
items_breakdown[].final_pricefloatLine price after the rental discount (experiences are never discounted).
items_breakdown[].savingsfloatLine-level dollar savings.
Dollar savings, never the percentage

The response deliberately reports dollar savings, not the discount percentage. The percentage (a rental item's discount_tiers) is internal โ€” surface the final price and the dollar saving to the customer.

Validationโ€‹

If the selection is structurally invalid, the preview returns a 422 whose error.details[] name the bad field and the valid options โ€” the same checks (and the same messages) as Create booking โ†’ Combo selection errors. Catches:

  • a rental durations value not in the item's allowed_durations;
  • a missing or invalid required choices entry;
  • a missing experience schedules entry.
{
"error": {
"code": "validation_error",
"message": "The given data was invalid.",
"details": [
{
"field": "durations.503",
"message": "Duration 3h is not allowed for 'Sea-Doo GTI 130'. Valid durations: 2h, 4h."
}
]
},
"meta": { "request_id": "req_abc123" }
}

Errorsโ€‹

StatusCodeDescription
401authentication_requiredNo API key provided
403insufficient_scopeAPI key lacks products:read scope
403forbiddenBundle belongs to a different supplier
404resource_not_foundBundle does not exist
422validation_errorMissing dates or an invalid combo selection (see Validation)

Prices by rate typeโ€‹

When pricing.type is rates, pricing.rate_prices lists the offered rate category IDs, labels, prices, and seat_weight. pricing.rate_price_basis is once, hour, or day. Bundles with experiences charge once; rental-only bundles can charge by duration.

Send rate_counts as a map of rate category ID to count, for example {"1": 2, "2": 1}. These counts apply to every selected experience and to gear configured for each person. Choose departures through the existing schedules map and rental lengths through durations. Use each bundle's offered IDs rather than assuming labels or IDs.

A combo rate counts as one sale unit and reserves the number of people in seat_weight. Gear uses quantity_basis (person or booking) and an optional rate_category_id; a null category applies to everyone. Unavailable items or incompatible experience rates reject the whole booking.

Required rental chargesโ€‹

The preview includes required per_rental_item charges for every applicable rental inside the selected bundle. extras_total and extra_lines show these charges separately; final_price includes them. The quantity follows the actual rental count, including multiple copies of a bundle. Optional extras still require a selection.