Two queries answer the only question a rental storefront really asks: can I have this, on these dates? Use rentalProductAvailability for a specific range the customer has picked, and rentalProductCalendar to paint a date picker for a whole month or season in one request. Neither needs a token.
Both are read from the reservations table at the moment you ask, so they never serve a stale answer, and both work in the product’s turnover-inclusive dates — see the overview for why that distinction matters.
rentalProductAvailability #
Ask whether a quantity of a product is free over one date range.
query CheckDates {
rentalProductAvailability(input: {
sku: "CANON-R6"
start_date: "2026-10-03 09:00:00"
end_date: "2026-10-05 17:00:00"
qty: 2
}) {
is_available
available_qty
total_qty
booked_qty
requested_qty
next_available_date
error { code message }
}
}
{
"data": {
"rentalProductAvailability": {
"is_available": false,
"available_qty": 1,
"total_qty": 4,
"booked_qty": 3,
"requested_qty": 2,
"next_available_date": "2026-10-06 09:00:00",
"error": {
"code": "NOT_ENOUGH_QUANTITY",
"message": "Only 1 of this item is available for the dates selected."
}
}
}
}
Reading the numbers #
The quantities describe the busiest moment anywhere inside the range, not the start of it and not an average. A five-day hire where three of four units are out for only the middle afternoon still reports available_qty: 1, because that afternoon is the constraint on the whole booking. That is the number you want — anything else would sell a booking that cannot be fulfilled.
total_qty— units in the fleet, before any bookings.booked_qty— units out at that busiest moment.available_qty—total_qtyminusbooked_qty.is_available— whether the booking can actually be made. This is not simplyavailable_qty >= requested_qty: it also enforces the product’s minimum and maximum rental period, disabled days, store opening hours and any same-day rule. A range can be refused with plenty of stock free.next_available_date— the soonest start that would work, when the answer is no.nullif it cannot be determined cheaply.
Why a range was refused #
When is_available is false, error.code says why. error.message is translated through Magento’s i18n and is safe to show a customer as-is.
error.code | What went wrong |
|---|---|
NOT_ENOUGH_QUANTITY | Fewer units free than asked for. |
BOOKED_DATES | Something inside the range is fully booked out. |
MINIMUM_PERIOD | Shorter than the product’s minimum hire. |
MAXIMUM_PERIOD | Longer than the product’s maximum hire. |
DISABLED_DATES | The range covers a day the product cannot go out on. |
START_DATE_DISABLED / END_DATE_DISABLED | That particular day is not selectable — typically a closed day for collections or returns. |
START_DATE_NOT_AVAILABLE | The start is earlier than the product’s first available date. |
SAME_DATES_ENFORCED | The product must start and end on the same day. |
SELECT_START_END_DATES | Both dates are required. |
NOT_ENOUGH_QUANTITY_BUYOUT | As above, for a buyout rather than a hire. |
UNKNOWN | A refusal this version of the schema does not have a name for. |
rentalProductCalendar #
Everything a date picker needs for one product over a window, in one round trip. Do not call rentalProductAvailability once per day to paint a month — this query builds the availability picture once and answers every day from it.
query PaintPicker {
rentalProductCalendar(input: {
sku: "CANON-R6"
start_date: "2026-10-01"
end_date: "2026-10-31"
qty: 1
}) {
total_qty
first_available_date
first_date_all_reserved
fully_booked_days
disabled_days
days {
date
available_qty
booked_qty
is_available
is_disabled
}
}
}
{
"data": {
"rentalProductCalendar": {
"total_qty": 4,
"first_available_date": "2026-10-01",
"first_date_all_reserved": "2026-10-12",
"fully_booked_days": ["2026-10-12", "2026-10-13"],
"disabled_days": ["2026-10-04", "2026-10-11", "2026-10-18", "2026-10-25"],
"days": [
{ "date": "2026-10-01", "available_qty": 4, "booked_qty": 0, "is_available": true, "is_disabled": false },
{ "date": "2026-10-04", "available_qty": 4, "booked_qty": 0, "is_available": false, "is_disabled": true },
{ "date": "2026-10-12", "available_qty": 0, "booked_qty": 4, "is_available": false, "is_disabled": false }
]
}
}
}
dayshas one entry per day in the window, inclusive of both ends.is_disabledmeans the product cannot go out that day regardless of stock — a Sunday, a configured closed day, a blackout period. Note the 4th of October above: four units free, still not bookable.fully_booked_daysare days with nothing free. A day can appear in both lists.is_availablealready combines both: it is true only when at leastqtyunits are free and the day is not disabled. If you only read one field per day, read this one.- Raising
qtynarrows the calendar — a day with two units free is available forqty: 1and not forqty: 3. Pass the quantity the customer has actually selected.
The window is capped at 400 days. A longer one is refused rather than served slowly. Request a season at a time and cache it on the client; a month is the natural unit for most pickers.
Checking dates while editing an existing booking #
If you re-check availability while editing a booking that already exists, its own units are counted against it and a booking that fits perfectly well looks unavailable. Both queries take exclude_reservation_ids (and exclude_order_id for a whole order) to answer as though those bookings were not there.
query CanIMoveThisBooking {
rentalProductAvailability(input: {
sku: "CANON-R6"
start_date: "2026-10-04 09:00:00"
end_date: "2026-10-07 17:00:00"
qty: 2
exclude_reservation_ids: [8123]
}) {
is_available
available_qty
}
}
These two inputs need a token holding SalesIgniter_Rental::manualedit. They let a caller see availability as if arbitrary bookings did not exist, which is a back-office capability, so an unauthenticated caller supplying them is refused rather than having them silently ignored.
Multi Source Inventory #
On a store running the MSI add-on, pass source_code or stock_id to ask about one location rather than the whole fleet — what a branch-picker on a multi-depot storefront needs. On a store without MSI both inputs are simply ignored.
rentalProductAvailability(input: {
sku: "CANON-R6"
start_date: "2026-10-03 09:00:00"
end_date: "2026-10-05 17:00:00"
source_code: "manchester"
}) { is_available available_qty }
