Selling rentals on a Hydrogen or custom storefront? A publishable key lets its pages show the rental calendar, price the dates and reserve them, then add the rental to a Shopify cart. Checkout prices and checks the line just as it does on your online store. This page is for the storefront’s developer. Your own servers use the secret-key API instead: see the API overview.
Make a publishable key #
- In Rentals & Bookings, open Settings › API › Headless storefronts and click Create a key.
- Enter a Key name (like “Hydrogen storefront”) and the Allowed websites, one per line:
https://shop.example.com,https://*.example.comfor all its subdomains (handy for preview deployments; it doesn’t includeexample.comitself), orhttp://localhost:3000while developing (plainhttponly works for localhost). No paths. - Give the Storefront API address and the key to your storefront’s developer.
A publishable key starts with sirpk_. It is not a secret: it goes in your storefront’s code, and you can copy it again at any time. It only works from the websites you list, and only for what a shopper can do on your online store: see the calendar, get a price, hold dates. It can’t read your orders or bookings or change anything.
Rotate makes a new key for the same name and websites, and keeps the old one working for 24 hours so you have time to update your storefront; Stop old key ends it early. Revoke stops a key at once. A store can have 10 active publishable keys, each with up to 20 websites.
How a headless booking works #
- The storefront asks for the product’s calendar (
calendar) and draws it: closed days, fully booked days, prices. - The shopper picks dates; the storefront shows the price (
quote). reserveholds the units for 30 minutes and answers the cart line: the variant, the quantity and the line attributes (the dates and a signed token).- The storefront adds that line to the Shopify cart with the Storefront API (
cartLinesAddorcartCreate) and your own Storefront API access token. - The shopper checks out. Shopify prices the rental line from the token and blocks checkout if the line doesn’t match it, exactly as on your online store. The order becomes a booking.
If the shopper changes the dates or removes the line before checkout, release the hold, reserve again and replace the cart line.
Addresses and authentication #
| API | Address |
|---|---|
| REST | https://YOUR-API-ADDRESS/sf/v1/your-store/… |
| GraphQL | POST https://YOUR-API-ADDRESS/graphql/2026-10/storefront/your-store |
your-store is the store’s myshopify name (your-store or your-store.myshopify.com); Settings › API › Headless storefronts shows the full address. Send the key in the X-Rentals-Key header (or Authorization: Bearer sirpk_…). A GET may carry it as ?key=sirpk_… instead, which saves the browser a preflight request.
- Allowed websites. A browser request from a website that isn’t listed for the key answers
403 origin_not_allowed, without CORS headers, so the browser blocks it. Listed websites getAccess-Control-Allow-Originwith their own address. No cookies are used: keepfetch‘scredentialsat the default. - Requests without an Origin (server-side rendering, curl, a native app) are allowed with a valid key.
- Call
reservefrom the shopper’s browser, not from your server. The limits and the hold cap count per shopper IP address; a server that forwards every shopper’s request looks like one shopper and hits them at once. This matters on Hydrogen: call the API from a component, not from a loader, and allow the address in the Content Security Policy (createContentSecurityPolicy({connectSrc: ['https://YOUR-API-ADDRESS']})).
A missing key answers 401 missing_key; an unknown, revoked or expired key, or another store’s key, answers 401 invalid_key.
REST endpoints #
REST errors have the shape {"error": {"code": "…", "message": "…", "field": "…"}}. The message is written for shoppers and can be shown as it is. Product and variant ids are Shopify’s numeric ids (the digits at the end of the gid).
GET /sf/v1/{shop}/calendar #
Everything a date picker needs for one product, in the same format the store’s own rental calendar uses.
| Parameter | Notes |
|---|---|
product, variant | Numeric ids. With only product, its first rent variant. |
from, to | The days to cover (Y-m-d). Default 90 days from from, at most 400. Without from, everything from two days ago on. |
location | The pick-up location, for stores that rent from several places. |
method, returnMethod | Delivery & pickup: pickup, delivery or shipped; in_store, collect or post. |
slim | 1 for a lighter answer without day prices. |
curl -s "https://YOUR-API-ADDRESS/sf/v1/your-store/calendar?product=88001&variant=44001&from=2026-12-01&to=2026-12-31" \
-H "X-Rentals-Key: sirpk_your_publishable_key"
# 200, Cache-Control: public, max-age=30 (trimmed)
{
"calendarType": "date", "productid": 88001, "variant": 44001, "currency": "USD", "today": "2026-11-30",
"qtyinstock": 4, "inventoryQuantity": 4,
"disabled_dates": [{"from": "2026-12-06", "to": "2026-12-06 00:00:00", "reason": "Closed on Sundays"}],
"fullybooked_dates": [], "fixeddates": [], "dayPrices": [],
"minLength": [], "maxLength": [], "minDate": "2026-11-30 09:00:00", "maxFutureDaysDate": "",
"storeopen": "08:00:00", "storeclose": "18:00:00", "lengthUnit": "day",
"window": {"from": "2026-12-01", "to": "2026-12-31"}, "locations": [], "location": null, "…": "…"
}
POST /sf/v1/{shop}/quote #
The price of a rental over these dates, with the breakdown. It prices anything that parses; availability and the store’s rules are checked by reserve.
| Body field | Notes |
|---|---|
product, variant | Numeric ids; variant is required. |
quantity | Default 1. |
from, to | Y-m-d; to defaults to from. |
fromtime, totime | HH:mm, for rentals by the hour. |
guests | For products that count guests: a number, or {adults, children, infants, pets}. |
location, method, returnMethod, window, backWindow | As in the calendar; window is a time slot such as 08:00-10:00. |
payment | part to pay part now and the rest later, when the store and the rental allow it. |
locale | The storefront’s language for texts, such as fr. |
curl -s -X POST "https://YOUR-API-ADDRESS/sf/v1/your-store/quote" -H "X-Rentals-Key: sirpk_your_publishable_key" \
-H "Content-Type: application/json" -d '{"product": 88001, "variant": 44001, "quantity": 1, "from": "2026-12-02", "to": "2026-12-04"}'
# 200
{"total": 900, "unit": 900, "quantity": 1, "start": "2026-12-02", "end": "2026-12-04 00:00:00", "days": 2, "hours": 48,
"lines": [{"label": "1 day × 2", "amount": 900, "kind": "rate"}], "extra_lines": [], "guests": [], "length_unit": "day", "currency": "USD"}
POST /sf/v1/{shop}/reserve #
Holds the dates for 30 minutes and answers the cart line. The body is the same as quote. The stock stays taken until the hold is released, expires, or turns into an order.
curl -s -X POST "https://YOUR-API-ADDRESS/sf/v1/your-store/reserve" -H "X-Rentals-Key: sirpk_your_publishable_key" \
-H "Content-Type: application/json" -d '{"product": 88001, "variant": 44001, "quantity": 1, "from": "2026-12-02", "to": "2026-12-04"}'
# 200
{
"token": "eyJ2Ijo…",
"properties": {"Rental dates": "Dec 2, 2026 – Dec 4, 2026", "_rental": "eyJ2Ijo…"},
"quote": {"quantity": 1, "total": 900, "unit": 900, "days": 2, "…": "…"},
"hold": {"id": "01kc2x7d9q3m5n8p0r4t6v1w3y", "expiresAt": "2026-11-30T15:42:10+00:00"}
}
Add the line to the cart with merchandiseId: "gid://shopify/ProductVariant/44001", quantity = quote.quantity and attributes = the properties as [{key, value}]. The quantity must be quote.quantity exactly (for a product that counts guests it can differ from what the shopper typed), and the attributes must be sent unchanged: they carry the signed token. Never change a rental line’s quantity on its own; reserve again instead. The answer also carries location, handover and payment when they apply.
Refusals: 409 not_available, 409 with a store rule (closed_day, start_too_early, min_length, max_length, too_far_ahead, fixed_length, fixed_date, guests), 422 invalid or bad_dates (with field), 422 location_required (the product is at several locations: send location), 422 method_required (the product offers several methods: send method), 404 not_rentable, 429 too_many_holds, 429 rate_limited, 503 not_ready (online booking isn’t set up on the store yet; Retry-After).
POST /sf/v1/{shop}/release #
Frees a hold: {"hold": "01kc2x7d9q3m5n8p0r4t6v1w3y"} answers {"ok": true}. It is safe to repeat, and an expired hold is fine.
curl -s -X POST "https://YOUR-API-ADDRESS/sf/v1/your-store/release" -H "X-Rentals-Key: sirpk_your_publishable_key" \
-H "Content-Type: application/json" -d '{"hold": "01kc2x7d9q3m5n8p0r4t6v1w3y"}'
# 200
{"ok": true}
GET /sf/v1/{shop}/availability #
For collection pages and search results: is each product a rental, and is it free for a date search? Up to 50 products.
| Parameter | Notes |
|---|---|
products or handles | Comma-separated product ids, or handles (for theme cards that carry no id). |
from, to | Y-m-d; to defaults to from. length (4h, 3d, 2w) instead of to. |
fromtime, totime, quantity, guests, location | Optional. |
sr_where, sr_radius | A place and a radius in kilometres: each rental also gets distanceKm and near. |
curl -s "https://YOUR-API-ADDRESS/sf/v1/your-store/availability?products=88001,88002&from=2026-12-02&to=2026-12-04" \
-H "X-Rentals-Key: sirpk_your_publishable_key"
# 200, Cache-Control: public, max-age=30
{"88001": {"rental": true, "available": true, "units": 3, "handle": "party-tent-6x12"}, "88002": {"rental": false}}
422 bad_request without products or dates.
GET /sf/v1/{shop}/handover/windows #
The time slots of a hand-over day, for stores with delivery & pickup. Parameters: product, variant, method, date (required, Y-m-d), location, back (1 for the return day), returnMethod, locale. Answers {mode, windows: [{id, from, to, label, capacity, left, full}]}.
Any other path under /sf answers 404 not_found, and a known endpoint with the wrong method 405 method_not_allowed.
A complete example (plain JavaScript in the browser) #
const RENTALS = 'https://YOUR-API-ADDRESS/sf/v1/your-store'; // Settings › API › Headless storefronts
const PK = 'sirpk_your_publishable_key'; // public: fine in the browser
const SHOP = 'https://your-store.myshopify.com/api/2026-07/graphql.json';
const STOREFRONT_TOKEN = 'your-storefront-api-token'; // your own Storefront API access token
async function rentals(path, body) {
const res = await fetch(`${RENTALS}/${path}`, body
? { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Rentals-Key': PK }, body: JSON.stringify(body) }
: { headers: { 'X-Rentals-Key': PK } });
const data = await res.json();
if (!res.ok) { // {error: {code, message, field?}}: the message is written for shoppers
throw Object.assign(new Error(data.error?.message || res.statusText), data.error, { status: res.status });
}
return data;
}
async function storefront(query, variables) {
const res = await fetch(SHOP, { method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': STOREFRONT_TOKEN },
body: JSON.stringify({ query, variables }) });
return (await res.json()).data;
}
const productId = 88001, variantId = 44001; // numeric ids: the digits at the end of the gid
const cal = await rentals(`calendar?product=${productId}&variant=${variantId}&from=2026-12-01&to=2026-12-31`);
// … draw a calendar from cal.disabled_dates, cal.fullybooked_dates, cal.dayPrices, cal.minLength …
const pick = { product: productId, variant: variantId, quantity: 1, from: '2026-12-02', to: '2026-12-04', locale: 'en' };
const quote = await rentals('quote', pick); // show quote.total
const reply = await rentals('reserve', pick); // {token, properties, quote, hold}
const line = {
merchandiseId: `gid://shopify/ProductVariant/${variantId}`,
quantity: reply.quote.quantity,
attributes: Object.entries(reply.properties).map(([key, value]) => ({ key, value })),
};
const out = await storefront(`mutation($id: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $id, lines: $lines) { cart { id checkoutUrl } userErrors { code message } } }`,
{ id: cartId, lines: [line] }); // no cart yet? cartCreate(input: {lines: [line]})
if (out.cartLinesAdd.userErrors.length) {
await rentals('release', { hold: reply.hold.id });
throw new Error(out.cartLinesAdd.userErrors[0].message);
}
window.location.href = out.cartLinesAdd.cart.checkoutUrl;
On Hydrogen, hand the line to the cart route with CartForm.ACTIONS.LinesAdd (fetcher.submit({[CartForm.INPUT_NAME]: JSON.stringify({action: CartForm.ACTIONS.LinesAdd, inputs: {lines: [line]}})}, {method: 'POST', action: '/cart'})).
When checkout finds a rental line that doesn’t match its token (a changed quantity, a missing attribute, dates in the past), the cart mutations answer a VALIDATION_CUSTOM user error naming the item and checkout is blocked. A hold that expired before the order is still booked when stock remains; otherwise the booking is flagged as a conflict for the store to sort out, just like on the online store.
The storefront GraphQL API #
The same publishable key opens POST https://YOUR-API-ADDRESS/graphql/2026-10/storefront/your-store, with the same allowed websites, limits and codes. reserveRentalDates answers the cart line ready for cartLinesAdd: merchandiseId, lineQuantity and attributes. A refusal is a userErrors entry (code as in REST, httpStatus the REST status) instead of an HTTP error. Introspection is off for storefronts: use the tables below or the explorer with a secret key.
const RENTALS_GQL = 'https://YOUR-API-ADDRESS/graphql/2026-10/storefront/your-store';
async function rentals(query, variables = {}) {
const res = await fetch(RENTALS_GQL, { method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Rentals-Key': 'sirpk_your_publishable_key' },
body: JSON.stringify({ query, variables }) });
const body = await res.json();
if (body.errors?.length) throw Object.assign(new Error(body.errors[0].message), body.errors[0].extensions);
return body.data;
}
const pick = { variantId: '44001', quantity: 1, startDate: '2026-12-02', endDate: '2026-12-04' };
const { rentalProductPrice: price } = await rentals(
`query($i: RentalPriceInput!) { rentalProductPrice(input: $i) { total unitPrice days currency } }`, { i: pick });
const { reserveRentalDates: r } = await rentals(
`mutation($i: RentalReserveInput!) { reserveRentalDates(input: $i) {
merchandiseId lineQuantity attributes { key value } hold { id expiresAt } userErrors { code message httpStatus } } }`, { i: pick });
if (r.userErrors.length) throw new Error(r.userErrors[0].message); // not_available, closed_day, too_many_holds, not_ready …
const line = { merchandiseId: r.merchandiseId, quantity: r.lineQuantity, attributes: r.attributes };
// cartLinesAdd(cartId, lines: [line]) with your Storefront API token. Changed dates or a removed line:
// await rentals(`mutation($h: ID!) { releaseRentalHold(holdId: $h) { released } }`, { h: r.hold.id });
A storefront key may use these operations only; asking for anything else refuses the whole request with access_denied.
| Operation | Arguments | What it does |
|---|---|---|
rentalShopquery · secret or publishable key returns RentalShop! | – | The store the key belongs to: domain, name, currency, time zone, today’s date and whether online booking is ready (checkoutReady). |
rentalLocationsquery · secret or publishable key returns [RentalLocation!]! | – | The store’s Shopify locations and whether rentals are picked up there. Storefronts see only the locations that rent, without phone numbers. |
rentalProductCalendarquery · secret or publishable key returns RentalCalendar! | input: RentalCalendarInput! | What a date picker needs for one product: fully booked days, the booked intervals (secret keys only) and the full calendar payload the store’s own calendar uses (widgetConfig). |
rentalProductPriceListquery · secret or publishable key returns RentalPriceList! | productId: IDvariantId: ID | The rate card: every period the product is priced by, its price and its price per day. |
rentalProductPricequery · secret or publishable key returns RentalQuote! | input: RentalPriceInput! | The price of a rental over these dates, with the breakdown: exactly what the cart will charge. It prices anything that parses; availability and the store’s rules are checked when you reserve or book. |
rentalProductDayPricesquery · secret or publishable key returns RentalDayPrices! | input: RentalDayPricesInput! | The price of a one-day rental starting on each day of a range (at most 366 days). |
rentalCatalogAvailabilityquery · secret or publishable key returns RentalCatalogAvailability! | productIds: [ID!]handles: [String!]selection: RentalSearchSelectionInput! | For up to 50 products (ids or handles): is each a rental, and is it free for a date search? With a place (where), how far away it is. |
rentalMapMarkersquery · secret or publishable key returns RentalMap! | collection: StringproductIds: [ID!]selection: RentalSearchSelectionInput | Map markers for rental products with a location (at most 250), optionally for a collection, some products or a place; with dates, whether each is free. |
rentalHandoverWindowsquery · secret or publishable key returns [RentalHandoverWindow!]! | input: RentalHandoverWindowsInput! | The time slots of a hand-over day for a product and method (pick-up, delivery or posted). |
rentalKitquery · secret or publishable key returns RentalKit | id: ID! | One kit, or null. |
reserveRentalDatesmutation · publishable key only returns RentalReservePayload! | input: RentalReserveInput! | Hold the dates for 30 minutes and get the cart line: merchandiseId, lineQuantity and attributes for the Storefront API’s cartLinesAdd. |
releaseRentalHoldmutation · publishable key only returns RentalReleasePayload! | holdId: ID! | Free a hold (the shopper changed the dates or removed the line). Safe to repeat: a released or expired hold answers released: false. |
Their inputs:
RentalCalendarInput – A window to describe for a date picker. Give variantId, or productId (its first rent variant for the storefront).
| Field | Type | Notes |
|---|---|---|
productId | ID | |
variantId | ID | |
startDate | String | First day, Y-m-d. With it, the calendar payload covers startDate to endDate (default 90 days, at most 400); without it, everything from two days ago on. |
endDate | String | Last day, Y-m-d. |
slim | Boolean | A lighter calendar payload (no day prices). |
display | String | global: the calendar as the store-wide calendar shows it. |
locationId | ID | The pick-up location: the calendar is for that location’s stock and bookings. |
method | RentalHandoverMethod | The calendar for this method (its transit and hand-over days; the payload’s handover). |
returnMethod | RentalReturnMethod |
RentalPriceInput – What to price. Give variantId, or productId when the product has one rent variant.
| Field | Type | Notes |
|---|---|---|
productId | ID | |
variantId | ID | |
quantity | Int = 1 | |
startDate | String! | Start date (Y-m-d), or date and time (2026-10-01T09:00). |
endDate | String | End date; default the start date (one day). |
startTime | String | Start time (HH:mm) when startDate has none. |
endTime | String | End time (HH:mm) when endDate has none. |
guests | RentalPartyInput | The party, for a product that counts guests. |
locale | String | The storefront language for texts such as the Rental dates attribute, e.g. fr. |
locationId | ID | The pick-up location (prices don’t depend on it yet). |
method | RentalHandoverMethod | The method (prices don’t depend on it yet). |
returnMethod | RentalReturnMethod |
RentalPartyInput – A party of guests, for products that count guests. Leave it out for products that don’t.
| Field | Type | Notes |
|---|---|---|
guests | Int | Total guests, when the product doesn’t split them. |
adults | Int | |
children | Int | |
infants | Int | |
pets | Int |
RentalDayPricesInput
| Field | Type | Notes |
|---|---|---|
productId | ID | |
variantId | ID | |
startDate | String | First day, Y-m-d; default today. |
endDate | String | Last day, Y-m-d; default 30 days after the start. At most 366 days. |
RentalSearchSelectionInput – A date search (the search block’s selection; the theme’s sr_* URL parameters).
| Field | Type | Notes |
|---|---|---|
from | String | First day, Y-m-d. |
to | String | Last day, Y-m-d (default from). |
fromTime | String | HH:mm |
toTime | String | HH:mm |
quantity | Int | |
length | String | A length code instead of to: 4h, 3d, 2w, 1m, 1y. |
guests | Int | |
adults | Int | |
children | Int | |
infants | Int | |
pets | Int | |
where | String | A place as typed (≤ 100 characters): the shop’s own places first, then the geocoder. |
radiusKm | Int | Kilometres around where, 1–500 (default 25). |
locationId | ID | A pick-up location (the theme’s sr_loc): available THERE; without it, available at some location. |
RentalHandoverWindowsInput
| Field | Type | Notes |
|---|---|---|
productId | ID | |
variantId | ID | |
method | RentalHandoverMethod | |
returnMethod | RentalReturnMethod | |
date | String! | The day (Y-m-d). |
locationId | ID | |
back | Boolean | The return day’s slots instead of the out day’s. |
RentalReserveInput – What to hold: the same as the price input.
| Field | Type | Notes |
|---|---|---|
productId | ID | |
variantId | ID | |
quantity | Int = 1 | |
startDate | String! | Start date (Y-m-d), or date and time (2026-10-01T09:00). |
endDate | String | End date; default the start date (one day). |
startTime | String | Start time (HH:mm) when startDate has none. |
endTime | String | End time (HH:mm) when endDate has none. |
guests | RentalPartyInput | The party, for a product that counts guests. |
locale | String | The storefront language for the Rental dates text, e.g. fr. |
locationId | ID | Where the customer picks it up; required when the product is at two or more locations (location_required). |
payment | RentalPaymentChoice = FULL | PART to pay part now and the rest later, when the store and the rental allow it (else the rental is paid in full). Default FULL. |
method | RentalHandoverMethod | How it reaches the customer; required when the product offers two or more (method_required). |
returnMethod | RentalReturnMethod | |
window | String | The out time slot (08:00-10:00) when the method has slots. |
backWindow | String |
| Enum | Values |
|---|---|
RentalHandoverMethod | PICKUP – The customer picks it up.DELIVERY – The store delivers it.SHIPPED – It is posted (shipped). |
RentalReturnMethod | IN_STORE – The customer brings it back to the store.COLLECT – The store collects it.POST – The customer posts it back. |
RentalPaymentChoice | FULLPART |
And their types. Fields marked “secret key only” are refused with a publishable key:
| Type | Fields |
|---|---|
RentalShopThe store the credential belongs to. | domain: String!, name: String, currency: String!, timezone: String!, today: String!, primaryLocale: String, checkoutReady: Boolean! |
RentalLocationA Shopify location of the store, as a place to pick up rentals. | id: ID!, legacyResourceId: Int!, name: String!, shopifyName: String, publicName: String, address: RentalLocationAddress, area: String, active: Boolean!, rentalsEnabled: Boolean!, isDefault: Boolean!, acceptsReturns: Boolean!, pickupEnabled: Boolean!, instructions: String |
RentalLocationAddress | address1: String, address2: String, city: String, province: String, zip: String, country: String, countryCode: String, phone: String (secret key only) |
RentalCalendarAvailability for one product over a window. | productId: ID!, variantId: ID!, totalQuantity: Int!, window: RentalDateWindow, today: String!, currency: String!, fullyBookedDays: [RentalBookedDay!]!, inventory: [RentalInventoryInterval!]! (secret key only), widgetConfig: JSON!, product: RentalProduct! |
RentalDateWindowA range of days, Y-m-d. | from: String!, to: String! |
RentalBookedDayA day with every unit taken. | date: String!, quantity: Int! |
RentalInventoryIntervalUnits taken over an interval, turnaround included. | quantity: Int!, start: String!, end: String! |
RentalProductA rent variant: a product variant this store rents out. | productId: ID!, variantId: ID!, title: String, variantTitle: String, sku: String, rentalType: String, hasTime: Boolean!, mode: String!, units: Int, enabled: Boolean! |
RentalPriceListThe product’s rate card. | productId: ID!, variantId: ID!, name: String, variantTitle: String, rentalType: String, priceType: String, from: RentalPriceFrom!, headline: RentalPriceHeadline!, rates: [RentalRate!]!, minLength: JSON, maxLength: JSON, currency: String!, currencySymbol: String |
RentalPriceFrom | amount: Float!, unit: String |
RentalPriceHeadline | amount: Float!, period: String |
RentalRateOne period of the rate card. | label: String!, period: String!, quantity: Int!, unit: String!, days: Float!, price: Float!, perDay: Float, addOn: RentalRateAddOn |
RentalRateAddOn | label: String!, period: String!, price: Float! |
RentalQuoteWhat a rental costs: the storefront’s quote, what the cart line will charge. | productId: ID!, variantId: ID!, startDate: String!, endDate: String!, quantity: Int!, days: Int!, hours: Float!, unitPrice: Float!, total: Float!, lines: [RentalQuoteLine!]!, extraLines: [RentalQuoteLine!]!, guests: JSON, lengthUnit: String, currency: String!, currencySymbol: String, chargedTotal: Float!, tokenUnitPrice: String!, payment: RentalPaymentOptions |
RentalQuoteLineOne line of a price breakdown. | label: String!, kind: String!, amount: Float! |
RentalPaymentOptionsHow a rental can be paid (the store’s Settings › Payments). Present when part payment can apply to the product. | options: [RentalPaymentChoice!]!, default: RentalPaymentChoice!, part: RentalPaymentSplit, reason: String, message: String |
RentalPaymentSplitPart now, the balance later: decimal strings in the store currency. | payNow: String!, balance: String!, dueDate: String!, dueAt: String, dueLabel: String, percent: Float, text: String |
RentalDayPricesA one-day hire’s price starting on each day. | productId: ID!, variantId: ID!, startDate: String!, endDate: String!, note: String!, days: [RentalDayPrice!]!, currency: String!, currencySymbol: String |
RentalDayPrice | date: String!, amount: Float! |
RentalCatalogAvailability | items: [RentalCatalogEntry!]!, where: RentalWhere |
RentalCatalogEntry | key: String!, productId: ID, handle: String, rental: Boolean!, available: Boolean, units: Int, distanceKm: Float, near: Boolean |
RentalWhereA place a Where search resolved to. | resolved: Boolean!, label: String, lat: Float, lng: Float, radiusKm: Int |
RentalMap | markers: [RentalMapMarker!]!, where: RentalWhere |
RentalMapMarker | productId: ID!, handle: String!, title: String!, url: String!, image: String, lat: Float!, lng: Float!, radius: Float, approximate: Boolean!, area: String, from: String, available: Boolean, near: Boolean, distanceKm: Float |
RentalHandoverWindowA time slot of a hand-over day. | id: String!, from: String!, to: String!, label: String, capacity: Int, left: Int, full: Boolean! |
RentalKit | id: ID!, legacyResourceId: Int!, productId: ID!, variantId: ID!, title: String!, source: RentalKitSource!, pricing: RentalKitPricing!, itemsDiscountPercent: String, useItemRules: Boolean!, items: [RentalKitItem!]! |
RentalKitItem | productId: ID!, variantId: ID!, title: String!, variantTitle: String, quantity: Int! |
RentalReservePayload | token: String, attributes: [RentalAttribute!], properties: JSON, lineQuantity: Int, merchandiseId: ID, quote: RentalQuote, hold: RentalHold, product: RentalProduct, datesText: String, userErrors: [RentalUserError!]!, location: RentalLocation, payment: RentalReservePayment, handover: RentalHandover |
RentalAttributeA cart line attribute, ready for the Storefront API’s CartLineInput.attributes. | key: String!, value: String! |
RentalHoldDates held for a shopper until checkout (or until it expires). | id: ID!, legacyResourceId: String!, expiresAt: String! |
RentalReservePaymentHow a held rental will be paid (what the token carries). | choice: RentalPaymentChoice!, part: RentalPaymentSplit, reason: String, message: String |
RentalHandoverA booking’s hand-over: the out moment (pick-up, delivery or dispatch day) and the back moment (return, collection or the day the parcel is due back). | method: RentalHandoverMethod!, returnMethod: RentalReturnMethod!, label: String, returnLabel: String, outAt: String, outWindow: String, backAt: String, backWindow: String, outLabel: String, backLabel: String, text: String, transitOutMinutes: Int!, transitBackMinutes: Int!, mismatch: JSON, returnTracking: String (secret key only), deliveryAddress: JSON (secret key only) |
RentalReleasePayload | released: Boolean!, userErrors: [RentalUserError!]! |
| Enum | Values |
|---|---|
RentalKitSource | APP – Made in the app or through this API.SHOPIFY_BUNDLES – A Shopify Bundles product run as a fixed-price kit. |
RentalKitPricing | OWN – The kit’s own rental rates.ITEMS_SUM – The sum of the items’ rental prices for the dates, minus itemsDiscountPercent. |
Limits #
| What | Limit | Answer |
|---|---|---|
| Requests a minute, per store and shopper IP address | reserve 20, quote 60, calendar 120 (shared by REST, GraphQL and the online store’s calendar) | 429 rate_limited with Retry-After |
| Live holds per shopper IP address | 10; release or the 30-minute expiry frees one | 429 too_many_holds |
| A hold | 30 minutes; the stock stays taken until then | |
| Online booking not set up yet | reserve refuses, so no line can check out at the wrong price | 503 not_ready with Retry-After |
| Keys and websites | 10 active keys per store, 20 websites per key |
calendar and availability may be cached by the browser for 30 seconds (Cache-Control: public, max-age=30); everything else is no-store. In GraphQL, GET requests made only of rentalProductCalendar, rentalCatalogAvailability, rentalProductPriceList and rentalProductDayPrices (30 seconds) or rentalMapMarkers (60 seconds) are cached the same way.
