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β
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 15 | Items per page (max 100) |
status | string | - | Filter by status: active, draft, disabled |
category_id | integer | - | Filter by category ID |
search | string | - | Search by name or SKU |
include_unlisted | boolean | false | Include products that are not bookable on their own (list_as_rental: false), which are excluded by default. |
public | boolean | false | Only what the public booking site shows: leaves out Staff only products as well. Staff only products are listed by default. |
sort | string | - | Sort field: id, name, sku, created_at, status, sort_order. Prefix with - for descending (e.g. -name). |
lang | string | en | Return 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β
| Field | Type | Description |
|---|---|---|
id | integer | Unique product identifier |
name | string | Product name |
sku | string | Stock keeping unit |
status | string | active, draft, or disabled |
brief_description | string | Short description |
long_description | string | Full description |
category | object|null | Category details |
pricing | object | Pricing configuration |
from_price | float|null | The 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_unit | string|null | What 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_constraints | object|null | Min/max rental period |
min_booking_notice | object|null | Minimum 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_time | object|null | Buffer time between rentals |
inventory | boolean | Whether inventory tracking is enabled |
inventory_tracking | string|null | Tracking type: group or item (null when inventory is disabled) |
allow_shortage | boolean | Whether this product may keep taking bookings after on-hand stock reaches zero. Default false β see Keep selling when sold out |
max_shortage | integer|null | How 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_variations | boolean | Whether product has variations |
bundle_only | boolean | Only available as part of a bundle |
list_as_rental | boolean | false = 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_only | boolean | true = 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_cart | boolean | false when the shop left this product out of cart suggestions. |
who_can_book | string | Read only. everyone, staff_only, or off (marketplaces only for an experience, not bookable on its own for a product). |
compliance | object|null | Boats 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_participants | boolean | Whether participant details are collected at checkout β the per-product override resolved against the shop-wide default |
require_participants_override | boolean|null | Raw per-product setting: null = follows the shop-wide default, true/false = explicit override |
participants_per_unit | integer | How many people use one unit (a 2-person kayak = 2) |
variations | array | Product variations (sizes, colors, etc.) |
default_image | object|null | Primary product image |
custom_fields | array | Custom field values for this product |
created_at | string | ISO 8601 datetime |
updated_at | string | ISO 8601 datetime |
Custom Fieldsβ
| Field | Type | Description |
|---|---|---|
field_id | integer | Custom field definition ID |
label | string | Field display label |
field_type | string | Field type: text, textarea, number, date, datetime |
value | string|null | Raw value |
formatted_value | string|null | Formatted value for display |
Pricing Objectβ
| Field | Type | Description |
|---|---|---|
applies | string | yearRound or seasonal |
structure | string | flat or schedule |
uses_template | boolean | Whether using a pricing template |
template_id | integer | Pricing template ID (if using template) |
base_price | float | Base price (if using template) |
rules | array | Pricing rules for different seasons/tiers |
Errorsβ
| Status | Code | Description |
|---|---|---|
| 401 | authentication_required | No API key provided |
| 401 | invalid_api_key | Invalid API key |
| 403 | insufficient_scope | API 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",
"...": "..."
}
]
}