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โ
| Parameter | Type | Description |
|---|---|---|
id | integer | Bundle ID |
Request Bodyโ
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string | Yes | Start date/time (ISO 8601). For a combo, this is the activity start. |
end_date | string | Yes | End date/time (ISO 8601). Must be on or after start_date. |
quantity | integer | No | Number of bundles. Defaults to 1. |
choices | object | No (Yes for a combo) | { choice_group: bundle_item_id } โ the chosen option for each "pick one" group (from Get bundle โ choice_groups). |
durations | object | No | { 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. |
participants | object | No | { 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). |
variations | object | No | { bundle_item_id: variation_id } โ chosen variation for a rental item that has variations. |
schedules | object | No | { 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.
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โ
| Field | Type | Description |
|---|---|---|
bundle_id | integer | The bundle that was priced. |
quantity | integer | Number of bundles priced. |
pricing_type | string | calculated or flat. |
original_price | float | Sum of the chosen items at full price (before any combo discount). |
final_price | float | The combo total the customer pays. |
savings | float | Dollar savings (original_price โ final_price). Show this to the customer โ never the underlying discount percentage. |
security_deposit_amount | float|null | The single flat security hold for the combo (ร quantity), or null when deposits are inherited per item. |
items_breakdown | array | Per-line pricing for each chosen item. |
items_breakdown[].bundle_item_id | integer | The bundle item that was priced. |
items_breakdown[].item_type | string | product (rental) or experience. |
items_breakdown[].name | string | Product or experience name. |
items_breakdown[].quantity | integer | Total units for this line (item quantity ร bundle quantity). |
items_breakdown[].duration_hours | float|null | The chosen rental duration in hours (rental items only). Fractional values are minutes (0.5 = 30 min, 2.5 = 2h 30m). |
items_breakdown[].participants | integer|null | Heads on this line (experience items only). |
items_breakdown[].original_price | float | Line full price. |
items_breakdown[].final_price | float | Line price after the rental discount (experiences are never discounted). |
items_breakdown[].savings | float | Line-level dollar savings. |
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
durationsvalue not in the item'sallowed_durations; - a missing or invalid required
choicesentry; - a missing experience
schedulesentry.
{
"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โ
| Status | Code | Description |
|---|---|---|
| 401 | authentication_required | No API key provided |
| 403 | insufficient_scope | API key lacks products:read scope |
| 403 | forbidden | Bundle belongs to a different supplier |
| 404 | resource_not_found | Bundle does not exist |
| 422 | validation_error | Missing 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.