Skip to main content

Calculate Pricing

Calculate prices for multiple products, experiences, or bundles in a single request.

Endpointโ€‹

POST /v1/pricing/calculate

Request Bodyโ€‹

FieldTypeRequiredDescription
itemsarrayYesItems to price (max 50)
items[].typestringYesproduct, experience, or bundle
items[].product_idintegerIf type=productProduct ID
items[].experience_idintegerIf type=experienceExperience ID
items[].bundle_idintegerIf type=bundleBundle ID
items[].variation_idintegerNoProduct variation ID. When the product has variation pricing enabled and this variation carries its own pricing, the quote uses the variation's pricing; otherwise the product's base pricing applies.
items[].location_idintegerNoPickup location ID. When the product has location pricing enabled and this location carries its own pricing, the quote uses it. Most specific scope wins: variation-at-location โ†’ location โ†’ variation โ†’ base.
items[].quantityintegerNoQuantity (default: 1)
items[].rate_category_idintegerNoExperience rate category
items[].schedule_idintegerNoExperience schedule slot
items[].booking_option_idintegerNoSelected booking option. Prices, minimum group quantities, fees and discounts come from this option; omitted uses the default option
items[].participantsintegerNoGroup-priced options only. Actual headcount used for the optional per-participant charge. When omitted, the calculation uses quantity as the headcount.
items[].package_choicesarrayNoTour-package experiences only โ€” the chosen option per choice group. Each option's per-traveler price is folded into the experience line_total. Travelers = participants if sent, else quantity.
items[].package_choices[].group_idintegerIf package_choices presentChoice group ID
items[].package_choices[].option_idintegerIf package_choices presentSelected option ID within the group
items[].extrasarrayNoAdd-on extras for this product or experience line. Each extra's total is folded into the line line_total.
items[].extras[].extra_idintegerIf extras presentExtra ID
items[].extras[].quantityintegerNoExtra quantity (default: 1)
start_datestringYesStart date/time (ISO 8601)
end_datestringYesEnd date/time (ISO 8601)
customer_idintegerNoQuote for this customer, and the endpoint reads their address off the record. The simplest way to resolve a deposit rule that looks at where the customer is.
customer_countrystringNoThe customer's country, ISO 3166-1 alpha-2. Use these three when you have the location but not a customer record.
customer_subdivisionstringNoThe customer's state, province or region. Accepts a code (TX), a name (Texas) or a qualified reference (US-TX).
customer_postcodestringNoThe customer's postcode, for a deposit rule that matches on postcode prefix.
price_channelstringNoonline (default) or counter. counter quotes the shop's counter prices. The response returns price_channel and counter_prices.available (whether the shop prices the counter differently).

Security depositโ€‹

The response's security_deposit object is the hold this booking would need:

FieldTypeDescription
enabledbooleanWhether a hold applies at all
amountnumberThe hold, in the shop's currency
basisstring or nullWhich deposit rule set the figure, e.g. 28 days or longer. Null when the shop's flat deposit applied.

A shop can set deposit rules that change the hold for a long booking, or for a customer a long way from them. Pass customer_id (or the three customer_* location fields) and the endpoint resolves the same figure the booking page and the counter would; omit them and any rule that reads location is skipped. Where more than one rule matches, the highest amount wins.

You do not have to send the resolved amount back when you create the booking: POST /bookings applies the same rules itself whenever you leave security_deposit_amount off, so an integration gets the right hold without quoting first. Send an amount only to override the shop's rules on purpose.

The rules themselves are managed in the shop's settings. A shop's rules match on places inside its own country (states, provinces or regions), on postcode starts, and on how long the booking runs.

Example Requestโ€‹

curl -X POST "https://app.equipdash.com/api/v1/pricing/calculate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"type": "product",
"product_id": 1,
"quantity": 2
},
{
"type": "product",
"product_id": 5,
"variation_id": 12,
"quantity": 1
},
{
"type": "experience",
"experience_id": 3,
"schedule_id": 45,
"rate_category_id": 8,
"quantity": 2,
"extras": [
{ "extra_id": 7, "quantity": 2 }
]
},
{
"type": "experience",
"experience_id": 12,
"schedule_id": 88,
"rate_category_id": 14,
"quantity": 2,
"participants": 2,
"package_choices": [
{ "group_id": 4, "option_id": 9 },
{ "group_id": 5, "option_id": 11 }
]
}
],
"start_date": "2026-02-15T09:00:00Z",
"end_date": "2026-02-17T17:00:00Z"
}'

For a group-priced booking option with booking rules, the quote itemizes what the booking will charge: a below-minimum fee appears as below_min_fee ({ "amount": 200.00, "label": "Small-group fee" }) and a multi-group discount as volume_discount ({ "amount": 27.90 }). Both are already reflected in line_total. Render them as sub-lines without adding them on top. A quantity below the selected option's minimum with no fee configured returns an error for that item, exactly as creating the booking would.

For an experience priced per person with a group discount (people_discount_from and people_discount_percent), send every person type as its own item in the same request: the quote counts everyone on the experience across the items, and each item that earns the discount carries it as volume_discount ({ "amount": 20.00 }), already taken off its line_total.

For a tour package the experience base rate is typically 0 โ€” the entire price comes from the selected package_choices, priced per traveler. Send package_choices (and participants for the traveler count) and the choice total is folded into line_total.

Responseโ€‹

{
"data": {
"period": {
"start_date": "2026-02-15T09:00:00+00:00",
"end_date": "2026-02-17T17:00:00+00:00"
},
"currency": "USD",
"items": [
{
"index": 0,
"type": "product",
"product_id": 1,
"product_name": "Mountain Bike",
"variation_id": null,
"quantity": 2,
"unit_price": 60.00,
"line_total": 120.00,
"duration": "2 days",
"pricing_breakdown": [
{"label": "Day 1", "amount": 30.00},
{"label": "Day 2", "amount": 30.00}
]
},
{
"index": 1,
"type": "product",
"product_id": 5,
"product_name": "Kayak",
"variation_id": 12,
"quantity": 1,
"unit_price": 85.00,
"line_total": 85.00,
"duration": "2 days"
},
{
"index": 2,
"type": "experience",
"experience_id": 3,
"experience_name": "Sunset Kayak Tour",
"schedule_id": 45,
"rate_category_id": 8,
"rate_category_name": "Adult",
"quantity": 2,
"unit_price": 45.00,
"line_total": 90.00
}
],
"summary": {
"subtotal": 295.00,
"item_count": 3
}
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-02-01T10:00:00+00:00"
}
}

Response Fieldsโ€‹

Item (Product)โ€‹

FieldTypeDescription
indexintegerPosition in request array
typestringproduct
product_idintegerProduct ID
product_namestringProduct name
variation_idinteger|nullVariation ID
quantityintegerQuantity
unit_pricenumberPrice per unit
line_totalnumberTotal (unit_price ร— quantity)
durationstringHuman-readable duration
pricing_breakdownarrayLine-by-line pricing details

Item (Experience)โ€‹

FieldTypeDescription
typestringexperience
experience_idintegerExperience ID
experience_namestringExperience name
schedule_idintegerSchedule slot ID
price_per_participantnumberPresent for group pricing with a per-head charge โ€” the per-participant rate
participantsintegerPresent for group pricing with a per-head charge โ€” the headcount used
participants_totalnumberPresent for group pricing with a per-head charge โ€” participants x price_per_participant, included in line_total
rate_category_idinteger|nullRate category ID
rate_category_namestring|nullRate category name
quantityintegerNumber of participants
unit_pricenumberPrice per person
package_choices_totalnumberPresent for tour packages โ€” sum of the chosen options' per-traveler price ร— travelers, included in line_total
package_choice_linesarrayPresent for tour packages โ€” itemized chosen options (group_name, option_label, price, quantity)
extras_totalnumberPresent when extras were sent โ€” sum of the line's extras, included in line_total
extra_linesarrayPresent when extras were sent โ€” itemized extras (name, quantity, unit_price, price, charge_per)
line_totalnumberTotal price (base + per-participant + package choices + extras)

Products also accept extras and return extras_total / extra_lines in the same shape, folded into the product line_total.

Item (Error)โ€‹

If an item can't be priced:

{
"index": 0,
"type": "product",
"product_id": 999,
"error": "Product not found"
}

Pricing Breakdownโ€‹

For products with schedule/tiered pricing, pricing_breakdown shows how the price was calculated:

{
"pricing_breakdown": [
{"label": "First 2 hours", "amount": 25.00},
{"label": "Additional hours (3)", "amount": 15.00}
]
}

Notesโ€‹

  • Prices don't include taxes (applied at booking time)
  • Variation price adjustments are included in unit_price
  • Tour-package package_choices and line extras are priced with the same logic used when the booking is created, so the preview matches the final charge
  • A required choice group left unanswered, an invalid option, or an unavailable extra returns that item with an error
  • If an item has an error, it won't contribute to summary.subtotal
  • Maximum 50 items per request