Skip to main content

Get Bundle

Retrieve a single bundle with all its items.

A bundle may be a simple group of rental products or a combo that mixes rentals and an experience with "pick one" choice groups, a duration-tiered discount on the rental, and a single flat security hold. See Mixed bundles (combos) for the additional fields.

Endpointโ€‹

GET /v1/bundles/{id}

Authenticationโ€‹

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

Path Parametersโ€‹

ParameterTypeDescription
idintegerBundle ID

Example Requestโ€‹

curl -X GET "https://app.equipdash.com/api/v1/bundles/1" \
-H "Authorization: Bearer YOUR_API_KEY"

Responseโ€‹

Success (200 OK)โ€‹

{
"data": {
"id": 1,
"name": "Beach Day Package",
"description": "Everything you need for a perfect day at the beach including umbrella, chairs, and cooler.",
"excerpt": "Complete beach package",
"pricing": {
"type": "calculated",
"flat_price": null,
"interval_unit": null
},
"security_deposit_type": "inherit",
"security_deposit_amount": null,
"has_experience_items": false,
"items": [
{
"id": 1,
"item_type": "product",
"experience_id": null,
"choice_group": null,
"choice_label": null,
"variation_id": null,
"rate_category_id": null,
"display_label": "Beach Umbrella",
"discount_tiers": null,
"allowed_durations": null,
"product": {
"id": 10,
"name": "Beach Umbrella"
},
"variation": null,
"quantity": 1,
"discount": null,
"sort_order": 1
},
{
"id": 2,
"item_type": "product",
"experience_id": null,
"choice_group": null,
"choice_label": null,
"variation_id": null,
"rate_category_id": null,
"display_label": "Beach Chair",
"discount_tiers": null,
"allowed_durations": null,
"product": {
"id": 11,
"name": "Beach Chair"
},
"variation": {
"id": 5,
"name": "Blue"
},
"quantity": 2,
"discount": {
"type": "percentage",
"value": 10
},
"sort_order": 2
},
{
"id": 3,
"item_type": "product",
"experience_id": null,
"choice_group": null,
"choice_label": null,
"variation_id": null,
"rate_category_id": null,
"display_label": "Cooler",
"discount_tiers": null,
"allowed_durations": null,
"product": {
"id": 15,
"name": "Cooler"
},
"variation": null,
"quantity": 1,
"discount": {
"type": "flat",
"value": 5
},
"sort_order": 3
}
],
"choice_groups": [],
"always_included": [
{ "bundle_item_id": 1, "item_type": "product", "label": "Beach Umbrella" },
{ "bundle_item_id": 2, "item_type": "product", "label": "Beach Chair" },
{ "bundle_item_id": 3, "item_type": "product", "label": "Cooler" }
],
"total_items": 4,
"is_bookable": true,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-01-15T10:00:00Z"
},
"meta": {
"request_id": "req_abc123"
}
}

Response Fieldsโ€‹

FieldTypeDescription
idintegerBundle ID
namestringBundle name
descriptionstringFull description
excerptstringShort description
pricing.typestringflat or calculated
pricing.flat_pricefloat|nullFixed price (if flat pricing)
pricing.interval_unitstring|nullhour, day, or week (if flat pricing)
security_deposit_typestringinherit (sum the per-item product deposits) or flat (a single fixed hold for the whole combo). See Mixed bundles.
security_deposit_amountfloat|nullThe flat hold amount, in the supplier's currency. Only set (and only meaningful) when security_deposit_type is flat; null otherwise.
cancellation_policy_idinteger|nullOptional per-combo cancellation policy override. null means "apply the most rigid policy across the combo's items" (the default).
cancellation_policyobject|nullThe overriding policy (id, name) when cancellation_policy_id is set; null otherwise.
has_experience_itemsbooleantrue when at least one item is an experience โ€” i.e. this is a combo. Booking it produces a mixed booking.
itemsarrayBundle items
items[].item_typestringproduct (rental), experience, or store_product (a shop add-on, always full price). Exactly one of product / experience / store_product is populated to match.
items[].experience_idinteger|nullThe experience ID on an experience item; null otherwise.
items[].extra_participant_pricenumber|nullExperiences only. The per-participant charge (rate-category matched) applied to each participant beyond those included, e.g. 39.00 โ€” surface it as "charged per participant ($X)". Omitted on non-experience items; null when the experience has no positive per-head price.
items[].choice_groupstring|nullItems sharing the same non-null choice_group are "pick one" alternatives (e.g. sxs, watercraft). null = an always-included fixed item.
items[].choice_labelstring|nullThe customer-facing label for this option (e.g. "Large (2 seats)").
items[].variation_idinteger|nullRentals only. The one size or version this item is pinned to. null lets the customer pick.
items[].rate_category_idinteger|nullExperiences only. The one price this item books at. null uses the experience's first price.
items[].display_labelstringResolved label to show the customer โ€” choice_label when set, otherwise the product/experience name.
items[].discount_tiersobject|nullRentals only. A { hours: percent_off } map, e.g. { "2": 20, "4": 15 } โ€” 2h gets 20% off, 4h gets 15% off. Supersedes discount when present. Always null on experience items (experiences are never discounted in a combo).
items[].allowed_durationsarray|nullRentals only. The durations in hours this rental may take inside the bundle, e.g. [2, 4]. Fractional values mean minutes (0.5 = 30 min, 2.5 = 2h 30m). Enforces a minimum/whitelist that can be narrower than the product's standalone durations. null = any.
items[].productobject|nullProduct info (id, name, โ€ฆ) on a rental item.
items[].store_productobject|nullStore-product info (id, name, price, default_image) on a store_product item; null otherwise. Store products are always full price.
items[].variationobject|nullVariation info if applicable
items[].content_optionsarrayParts inside a rental kit that need an option choice. Each has part_id, label, quantity, and options (id, name, available). Without booking dates, available does not check stock for a window. Send picks in the booking's content_variations map. Empty for items without these choices.
items[].quantityintegerItem quantity
items[].discountobject|nullFlat per-item discount (type, value). For combos, prefer the duration-tiered discount_tiers when present.
items[].sort_orderintegerDisplay order
choice_groupsarrayAdditive convenience view of the pick-one structure โ€” one entry per non-empty choice_group, so you don't have to group the flat items array yourself. Empty ([]) for a simple bundle with no choice groups. The flat items array above is unchanged.
choice_groups[].choice_groupstringThe group name (e.g. "watercraft"). The customer picks one option from each group.
choice_groups[].optionsarrayThe options in this group โ€” each { bundle_item_id, item_type, label }. Send the chosen bundle_item_id as choices.{group} when booking.
always_includedarrayThe fixed (non-choice) items that ship with every combo โ€” each { bundle_item_id, item_type, label }. These have no choice_group and are always priced in.
total_itemsintegerSum of all item quantities
is_bookablebooleanWhether bundle can be booked

Mixed bundles (combos)โ€‹

A mixed bundle (combo) packages one or more rentals with an experience in a single bookable product. The customer picks one of each choice group, picks a rental duration, sees a combo total and dollar savings, and pays once. Booking a combo creates a single mixed booking.

How the extra fields fit together:

  • has_experience_items: true flags the bundle as a combo.
  • Choice groups โ€” items sharing a choice_group (e.g. sxs, watercraft) are alternatives; the customer picks exactly one from each group. Use choice_label / display_label for the option labels.
  • Duration-tiered discount โ€” a rental item's discount_tiers ( { hours: percent } ) and allowed_durations ( [2, 4] ) drive the deal on the rental half. Durations are in hours, and fractional values are minutes (0.5 = 30 min, 2.5 = 2h 30m) โ€” both the discount_tiers keys and the allowed_durations values. The experience is always full price โ€” its discount_tiers is always null.
  • Store-product add-ons โ€” an item with item_type: store_product is a shop add-on (e.g. a dry bag or a wetsuit sock). It carries a store_product block (id, name, price, default_image) and, like an experience, is always full price (no discount_tiers).
  • Cancellation policy โ€” set cancellation_policy_id to override the combo's cancellation policy; the matching policy id + name are echoed under cancellation_policy. Leave it null to apply the most rigid policy across the combo's items.
  • Flat security hold โ€” when security_deposit_type is flat, the combo carries a single security_deposit_amount hold covering both activities, instead of summing the per-item product deposits.
  • The percentage is internal. Customers should see the combo total and dollar savings computed at booking time (see the response of Create booking) โ€” discount_tiers exposes the underlying percentages for your own logic, not for display.

Example โ€” Biff Rentals "Discovery Tour" comboโ€‹

A guided side-by-side excursion (an experience, always $499) paired with a personal watercraft rental (the rental half gets the deal: 2h = 20% off, 4h = 15% off), under a single flat $4,000 security hold. The customer picks one side-by-side, one watercraft, and a 2h or 4h watercraft window.

{
"data": {
"id": 88,
"name": "Discovery Tour",
"description": "A guided side-by-side excursion plus a personal watercraft rental โ€” one combo price, one hold.",
"excerpt": "Side-by-side excursion + watercraft combo",
"pricing": {
"type": "calculated",
"flat_price": null,
"interval_unit": null
},
"security_deposit_type": "flat",
"security_deposit_amount": 4000,
"has_experience_items": true,
"items": [
{
"id": 501,
"item_type": "experience",
"experience_id": 1257,
"choice_group": "sxs",
"choice_label": "Can-Am Maverick X3 (2 seats)",
"display_label": "Can-Am Maverick X3 (2 seats)",
"discount_tiers": null,
"allowed_durations": null,
"experience": {
"id": 1257,
"name": "Discovery Tour Excursion โ€” X3"
},
"extra_participant_price": 39.00,
"product": null,
"variation": null,
"quantity": 1,
"discount": null,
"sort_order": 1
},
{
"id": 502,
"item_type": "experience",
"experience_id": 1260,
"choice_group": "sxs",
"choice_label": "Can-Am Maverick X3 MAX (4 seats)",
"display_label": "Can-Am Maverick X3 MAX (4 seats)",
"discount_tiers": null,
"allowed_durations": null,
"experience": {
"id": 1260,
"name": "Discovery Tour Excursion โ€” X3 MAX"
},
"extra_participant_price": 39.00,
"product": null,
"variation": null,
"quantity": 1,
"discount": null,
"sort_order": 2
},
{
"id": 503,
"item_type": "product",
"experience_id": null,
"choice_group": "watercraft",
"choice_label": "Sea-Doo GTI 130",
"display_label": "Sea-Doo GTI 130",
"discount_tiers": { "2": 20, "4": 15 },
"allowed_durations": [2, 4],
"product": {
"id": 163,
"name": "Sea-Doo GTI 130"
},
"variation": null,
"quantity": 1,
"discount": null,
"sort_order": 3
},
{
"id": 504,
"item_type": "product",
"experience_id": null,
"choice_group": "watercraft",
"choice_label": "Sea-Doo GTR-X 300",
"display_label": "Sea-Doo GTR-X 300",
"discount_tiers": { "2": 20, "4": 15 },
"allowed_durations": [2, 4],
"product": {
"id": 164,
"name": "Sea-Doo GTR-X 300"
},
"variation": null,
"quantity": 1,
"discount": null,
"sort_order": 4
}
],
"choice_groups": [
{
"choice_group": "sxs",
"options": [
{ "bundle_item_id": 501, "item_type": "experience", "label": "Can-Am Maverick X3 (2 seats)" },
{ "bundle_item_id": 502, "item_type": "experience", "label": "Can-Am Maverick X3 MAX (4 seats)" }
]
},
{
"choice_group": "watercraft",
"options": [
{ "bundle_item_id": 503, "item_type": "product", "label": "Sea-Doo GTI 130" },
{ "bundle_item_id": 504, "item_type": "product", "label": "Sea-Doo GTR-X 300" }
]
}
],
"always_included": [],
"total_items": 4,
"is_bookable": true,
"created_at": "2026-06-20T10:00:00Z",
"updated_at": "2026-06-20T10:00:00Z"
},
"meta": {
"request_id": "req_abc123"
}
}

choice_groups is the pick-one structure at a glance โ€” the customer picks one option from each group (here one side-by-side and one watercraft). It is purely additive: the same items are still in the flat items array. always_included lists any fixed items with no choice_group.

To book this combo, send the selection keys (choices, durations, participants, schedules, variations) in the bookings request โ€” see Create booking โ†’ Mixed-bundle combo. To compute the live total before booking, call Bundle price preview.

Errorsโ€‹

StatusCodeDescription
401authentication_requiredNo API key provided
403insufficient_scopeAPI key lacks products:read scope
403forbiddenBundle belongs to different supplier
404resource_not_foundBundle does not exist

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.