Skip to main content

List Products

Retrieve a paginated list of products (rental items).

Endpoint​

GET /v1/products

Authentication​

Requires an API key with the products:read scope.

Query Parameters​

ParameterTypeDefaultDescription
pageinteger1Page number
per_pageinteger15Items per page (max 100)
statusstring-Filter by status: active, draft, disabled
category_idinteger-Filter by category ID
searchstring-Search by name or SKU
include_unlistedbooleanfalseInclude products that are not bookable on their own (list_as_rental: false), which are excluded by default.
publicbooleanfalseOnly what the public booking site shows: leaves out Staff only products as well. Staff only products are listed by default.
sortstring-Sort field: id, name, sku, created_at, status, sort_order. Prefix with - for descending (e.g. -name).
langstringenReturn translated content fields (name, brief_description, long_description, custom_sections, category names) in the given language, e.g. fr. Only applies when the supplier has that booking widget language enabled β€” otherwise, and for any field without a translation yet, the English value is returned.
Default order

With no sort parameter, products are returned in the supplier's manual display order β€” the same order shown in the booking widget, POS, and app (category order, then product order within each category, as set on the Arrange order screen). Pass an explicit sort to override it.

Response​

Success (200 OK)​

{
"data": [
{
"id": 1,
"name": "Mountain Bike",
"sku": "MTB-001",
"status": "active",
"brief_description": "High-quality mountain bike for trail riding",
"long_description": "Full suspension mountain bike...",
"category": {
"id": 5,
"name": "Bikes"
},
"from_price": 50.00,
"from_price_unit": "day",
"pricing": {
"applies": "yearRound",
"structure": "flat",
"rate_mode": "total",
"uses_template": false,
"rules": [
{
"id": 1,
"season_id": null,
"price_structure": "flat",
"price": 50.00,
"interval": 1,
"label": null,
"interval_units": "Days",
"display_on_widget": true
}
]
},
"rental_constraints": {
"min": { "value": 1, "unit": "Days" },
"max": { "value": 7, "unit": "Days" }
},
"buffer_time": null,
"inventory": true,
"inventory_tracking": "group",
"allow_shortage": true,
"max_shortage": 2,
"has_variations": true,
"bundle_only": false,
"list_as_rental": true,
"staff_only": false,
"suggest_in_cart": true,
"who_can_book": "everyone",
"require_participants": false,
"require_participants_override": null,
"participants_per_unit": 1,
"variations": [
{
"id": 10,
"sku": "MTB-001-S",
"values": [
{ "property": "Size", "value": "Small" }
],
"image": null
},
{
"id": 11,
"sku": "MTB-001-L",
"values": [
{ "property": "Size", "value": "Large" }
],
"image": "https://example.com/images/mtb-large.jpg"
}
],
"default_image": {
"id": 1,
"url": "https://example.com/images/mtb.jpg"
},
"custom_fields": [
{
"field_id": 2,
"label": "Serial Number",
"field_type": "text",
"value": "SN-12345-ABC",
"formatted_value": "SN-12345-ABC"
}
],
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-15T10:30:00+00:00"
}
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 45,
"total_pages": 3,
"request_id": "req_abc123",
"timestamp": "2026-01-15T12:00:00+00:00"
}
}

Response Fields​

Product​

FieldTypeDescription
idintegerUnique product identifier
namestringProduct name
skustringStock keeping unit
statusstringactive, draft, or disabled
brief_descriptionstringShort description
long_descriptionstringFull description
categoryobject|nullCategory details
pricingobjectPricing configuration
from_pricefloat|nullThe price the booking page shows a customer first: a pricing template's shortest whole block (a 2-hour block at 295.00), otherwise the cheapest price row, otherwise base_price. null when the product has no price. Read this, not base_price, to show what a product costs: a product on an exact-price template stores base_price 1.00
from_price_unitstring|nullWhat from_price buys, as the booking page prints it after the slash: 2 hours, day, Half day. null when the length varies (seasonal template pricing) or there is no price
rental_constraintsobject|nullMin/max rental period
min_booking_noticeobject|nullMinimum booking notice (lead time). null when no notice applies. Object: value (int), unit (Minutes/Hours/Days), minutes (total minutes of the length), same_day_cutoff_time ("HH:MM" shop time when online bookings for the same day close, or null), inherited (true when using the supplier default rather than a per-product override). Bookings within this lead time are rejected across the widget, POS, API, and AI.
buffer_timeobject|nullBuffer time between rentals
inventorybooleanWhether inventory tracking is enabled
inventory_trackingstring|nullTracking type: group or item (null when inventory is disabled)
allow_shortagebooleanWhether this product may keep taking bookings after on-hand stock reaches zero. Default false β€” see Keep selling when sold out
max_shortageinteger|nullHow many units past on-hand stock may be sold for any overlapping window. One shared budget for the whole product, not per variation. null while allow_shortage is true means an allowance of 1 unit. Ignored when allow_shortage is false
has_variationsbooleanWhether product has variations
bundle_onlybooleanOnly available as part of a bundle
list_as_rentalbooleanfalse = equipment-only fleet (e.g. tour vehicles): hidden from rental listings but fully manageable. Such products are excluded from this index by default β€” pass include_unlisted=1 to include them. GET /products/{id} always returns them.
staff_onlybooleantrue = only the shop's team can book it (POS, the app, this API, Dash); hidden from the booking widget and embeds. Listed here by default; public=1 leaves it out.
suggest_in_cartbooleanfalse when the shop left this product out of cart suggestions.
who_can_bookstringRead only. everyone, staff_only, or off (marketplaces only for an experience, not bookable on its own for a product).
complianceobject|nullBoats only: inspection and insurance. null unless the product tracks them (product type Boats). Fields: inspection_expiry, inspection_document_location, insurance_policy_number, insurance_expiry, insurance_document_location (dates YYYY-MM-DD), inspection_status and insurance_status (each not_set, valid, expiring within 30 days, or expired), block_bookings_if_lapsed, status (the worse of the two), blocked (true while a date has lapsed and blocking is on: the boat, and every experience that uses it, cannot be booked)
require_participantsbooleanWhether participant details are collected at checkout β€” the per-product override resolved against the shop-wide default
require_participants_overrideboolean|nullRaw per-product setting: null = follows the shop-wide default, true/false = explicit override
participants_per_unitintegerHow many people use one unit (a 2-person kayak = 2)
variationsarrayProduct variations (sizes, colors, etc.)
default_imageobject|nullPrimary product image
custom_fieldsarrayCustom field values for this product
created_atstringISO 8601 datetime
updated_atstringISO 8601 datetime

Custom Fields​

FieldTypeDescription
field_idintegerCustom field definition ID
labelstringField display label
field_typestringField type: text, textarea, number, date, datetime
valuestring|nullRaw value
formatted_valuestring|nullFormatted value for display

Pricing Object​

FieldTypeDescription
appliesstringyearRound or seasonal
structurestringflat or schedule
uses_templatebooleanWhether using a pricing template
template_idintegerPricing template ID (if using template)
base_pricefloatBase price (if using template)
rulesarrayPricing rules for different seasons/tiers

Errors​

StatusCodeDescription
401authentication_requiredNo API key provided
401invalid_api_keyInvalid API key
403insufficient_scopeAPI key lacks products:read scope

Example Request​

curl -X GET "https://app.equipdash.com/api/v1/products?status=active&per_page=25" \
-H "Authorization: Bearer eqd_live_abc123xyz789..."

Translated Content​

Request the catalog in another enabled booking widget language with lang:

curl -X GET "https://app.equipdash.com/api/v1/products?status=active&lang=fr" \
-H "Authorization: Bearer eqd_live_abc123xyz789..."

Translated fields return the same values the supplier's booking widget shows in that language:

{
"data": [
{
"id": 1,
"name": "VΓ©lo de montagne",
"brief_description": "VTT de haute qualitΓ© pour les sentiers",
"...": "..."
}
]
}