Gear List
The gear list answers one question: what do I pull off the shelf for this booking, and from which site?
It covers the whole booking across all of its dates โ a multi-day rental, a bundle, a delivered event hire โ rather than just what is going out today. There is a matching endpoint for a program cohort, which rolls up every enrollment made against it.
Both endpoints read the same builder the EquipDash portal's Gear tab and the PDF/CSV exports use, so the API can never disagree with what staff see on screen.
The gear list reports on stock; it never reserves, holds, or releases anything.
Gear list for a bookingโ
GET /v1/bookings/{id}/gear-list
Requires the gear:read scope.
Gear list for a programโ
GET /v1/series/{id}/gear-list
Requires the gear:read scope, plus โ like every other series endpoint โ the experiences module. With that module switched off it returns 403 module_disabled.
The list is the union of every booking on the cohort, still peaked per date rather than summed (see below).
The product UI calls series Programs. The API keeps the series name in the URL; the response's subject.type is "program".
Query Parametersโ
Both endpoints accept the same optional filters. Omit them for the whole list.
| Parameter | Type | Description |
|---|---|---|
date | date (YYYY-MM-DD) | Only the gear needed on this supplier-local day |
activity | integer | Only the gear for this experience id |
Filters are applied to the source rows, before quantities are worked out โ so a filtered response's quantity is the peak for exactly what is returned. Never filter the full response client-side instead: that keeps the whole-booking peak and overstates a single day or a single activity.
Response (200 OK)โ
{
"data": {
"subject": { "type": "booking", "id": 1234 },
"filters": { "date": null, "activity": null },
"sections": [
{
"id": 12,
"name": "Morning session",
"items": [
{
"product_id": 44,
"variation_id": null,
"name": "Helmet",
"kit_parent": null,
"location_id": 3,
"location": "Main store",
"is_held": true,
"quantity": 6,
"available": 20,
"short_by": 0,
"units": ["H-001", "H-002"],
"dates": ["2026-04-22"]
}
]
},
{
"id": null,
"name": "Rentals on this booking",
"items": [
{
"product_id": 58,
"variation_id": 91,
"name": "Tent โ Capacity: 4 person",
"kit_parent": "Camp kit",
"location_id": null,
"location": "โ",
"is_held": false,
"quantity": 2,
"available": 1,
"short_by": 1,
"units": [],
"dates": ["2026-04-22", "2026-05-13"]
}
]
}
],
"dates": ["2026-04-22", "2026-05-13"],
"activities": [
{ "id": 12, "name": "Morning session" },
{ "id": 13, "name": "Afternoon session" }
],
"shortfalls": [
{
"product_id": 58,
"variation_id": 91,
"name": "Tent โ Capacity: 4 person",
"kit_parent": "Camp kit",
"location_id": null,
"location": "โ",
"is_held": false,
"quantity": 2,
"available": 1,
"short_by": 1,
"units": [],
"dates": ["2026-04-22", "2026-05-13"]
}
],
"totals": { "items": 2, "units": 8, "sections": 2, "dates": 2 }
},
"meta": {
"request_id": "req_01HZ...",
"timestamp": "2026-08-10T09:14:22+00:00"
}
}
Top-level fieldsโ
| Field | Type | Description |
|---|---|---|
subject | object | What the list was built for: { "type": "booking" | "program", "id": <int> }. |
filters | object | The filters that were applied: { "date": <string|null>, "activity": <int|null> }. Both null when nothing was narrowed. |
sections | array | The list itself, grouped by activity โ see below. |
dates | string[] | Every supplier-local date (YYYY-MM-DD) the returned gear is needed on, ascending. Use it to build a date picker. |
activities | array | { id, name } for every activity on the booking or program, whether or not it survived the filter โ so an activity picker keeps offering the others. Alphabetical by name. |
shortfalls | array | Just the items whose short_by is above zero, same item shape as in sections. Empty when there is enough of everything. |
totals | object | items (rows returned), units (their peak quantities added up), sections, dates. |
Sectionsโ
A section is an activity โ that is how a day is walked. Gear reserved against a plain rental line belongs to no activity and lands in its own bucket.
| Field | Type | Description |
|---|---|---|
id | integer|null | The experience id โ the value to pass as the activity filter. null marks the bucket for gear on a plain rental line, which belongs to no activity and so cannot be filtered to. |
name | string | The activity's name, or the localized "Rentals on this booking" label for the null bucket. |
items | array | The rows to pull, alphabetical by name. |
Sections come back alphabetically by activity name, with the no-activity bucket last.
Item fieldsโ
| Field | Type | Description |
|---|---|---|
product_id | integer | The product to pull. |
variation_id | integer|null | The specific variation, when the row is for one. |
name | string | Display name โ the product, plus the variation spelled out ("Tent โ Capacity: 4 person") when there is one. |
kit_parent | string|null | The parent item's name when this row is a part a kit drew (e.g. a part of "Camp kit"). null for an item booked in its own right. |
location_id | integer|null | The site to pull from. null when the stock has no site against it. |
location | string | The site's name, or "โ" when no site is set. A dash means nobody has said where the item lives โ which is not the same as "available everywhere". |
is_held | boolean | true when stock is actually reserved for this row. false means the activity says it needs the item but nothing is reserved yet โ see below. |
quantity | integer | How many are needed. This is the peak on any one date, never the sum across dates. |
available | integer | How many of this product (and variation) the shop owns at this site. Stock with no site set is a global pool and counts toward every site's figure. |
short_by | integer | max(0, quantity - available). Above zero means the row also appears in shortfalls. |
units | string[] | The readable tags of the specific items set aside, when individual units are tracked. Always empty when is_held is false. |
dates | string[] | The supplier-local dates (YYYY-MM-DD) this row is needed on, ascending. |
One row per activity and site: gear for the same product split across two sites stays two rows, each with its own units, because that is two trips.
Quantities are the peak, never the sumโ
quantity is the highest number needed at any one moment, not the total across the booking's dates.
Twelve of an item needed on one date and twelve on another is 12, not 24 โ the same twelve come back in between. Adding the dates up would send an owner hunting for stock they do not need. This is the same peak calculation the overbooking engine uses, so the two never disagree.
The same holds for a program: the cohort's list is the union of every enrollment, still peaked per date.
Because filters are applied before the peak is taken, quantity always matches what is on screen: ask for one date and you get that day's peak, not the whole booking's.
is_held: false โ needed, but not reserved yetโ
Reservations are written when a booking is created or edited. So if you attach gear to an activity today, every booking already on the books still reserves nothing for it โ and a list built only from reservations would show an empty shelf for an activity that plainly needs kit.
The gear list closes that gap: for any line with no reservation yet, it derives the requirement from the activity's current gear setup and returns the row with is_held: false.
Treat those rows as real requirements to prep against:
quantityis worked out from the activity's setup (per booking or per participant), on the same basis the reservation engine uses.unitsis empty andlocationfalls back to the product's own site when that is unambiguous โ nothing specific has been set aside, so no particular item can be named.- Nothing is double-counted: a line that does hold stock is never also derived.
Example Requestsโ
Everything on a booking:
curl "https://app.equipdash.com/api/v1/bookings/1234/gear-list" \
-H "Authorization: Bearer YOUR_API_KEY"
Just one day of a program, for one activity:
curl "https://app.equipdash.com/api/v1/series/1/gear-list?date=2026-05-13&activity=12" \
-H "Authorization: Bearer YOUR_API_KEY"
Errorsโ
| Status | Code | Description |
|---|---|---|
| 401 | authentication_required | No API key provided |
| 401 | invalid_api_key | Invalid API key |
| 403 | insufficient_scope | API key lacks the gear:read scope |
| 404 | resource_not_found | Booking or program not found, or it belongs to another account |
| 422 | validation_error | date is not YYYY-MM-DD, or activity is not an integer |
A bad date comes back as:
{
"error": {
"code": "validation_error",
"message": "The given data was invalid.",
"details": [
{ "field": "date", "message": "The date does not match the format Y-m-d." }
]
}
}