View Categories

Launching soon on the Shopify App StoreJoin the waitlist

Headless storefronts: the storefront API

18 min read

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 #

  1. In Rentals & Bookings, open Settings › API › Headless storefronts and click Create a key.
  2. Enter a Key name (like “Hydrogen storefront”) and the Allowed websites, one per line: https://shop.example.com, https://*.example.com for all its subdomains (handy for preview deployments; it doesn’t include example.com itself), or http://localhost:3000 while developing (plain http only works for localhost). No paths.
  3. 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 #

  1. The storefront asks for the product’s calendar (calendar) and draws it: closed days, fully booked days, prices.
  2. The shopper picks dates; the storefront shows the price (quote).
  3. reserve holds the units for 30 minutes and answers the cart line: the variant, the quantity and the line attributes (the dates and a signed token).
  4. The storefront adds that line to the Shopify cart with the Storefront API (cartLinesAdd or cartCreate) and your own Storefront API access token.
  5. 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 #

APIAddress
RESThttps://YOUR-API-ADDRESS/sf/v1/your-store/…
GraphQLPOST 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 get Access-Control-Allow-Origin with their own address. No cookies are used: keep fetch‘s credentials at the default.
  • Requests without an Origin (server-side rendering, curl, a native app) are allowed with a valid key.
  • Call reserve from 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.

ParameterNotes
product, variantNumeric ids. With only product, its first rent variant.
from, toThe days to cover (Y-m-d). Default 90 days from from, at most 400. Without from, everything from two days ago on.
locationThe pick-up location, for stores that rent from several places.
method, returnMethodDelivery & pickup: pickup, delivery or shipped; in_store, collect or post.
slim1 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 fieldNotes
product, variantNumeric ids; variant is required.
quantityDefault 1.
from, toY-m-d; to defaults to from.
fromtime, totimeHH:mm, for rentals by the hour.
guestsFor products that count guests: a number, or {adults, children, infants, pets}.
location, method, returnMethod, window, backWindowAs in the calendar; window is a time slot such as 08:00-10:00.
paymentpart to pay part now and the rest later, when the store and the rental allow it.
localeThe 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.

ParameterNotes
products or handlesComma-separated product ids, or handles (for theme cards that carry no id).
from, toY-m-d; to defaults to from. length (4h, 3d, 2w) instead of to.
fromtime, totime, quantity, guests, locationOptional.
sr_where, sr_radiusA 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.

OperationArgumentsWhat it does
rentalShop
query · 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).
rentalLocations
query · 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.
rentalProductCalendar
query · 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).
rentalProductPriceList
query · secret or publishable key
returns RentalPriceList!
productId: ID
variantId: ID
The rate card: every period the product is priced by, its price and its price per day.
rentalProductPrice
query · 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.
rentalProductDayPrices
query · 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).
rentalCatalogAvailability
query · 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.
rentalMapMarkers
query · secret or publishable key
returns RentalMap!
collection: String
productIds: [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.
rentalHandoverWindows
query · 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).
rentalKit
query · secret or publishable key
returns RentalKit
id: ID!One kit, or null.
reserveRentalDates
mutation · 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.
releaseRentalHold
mutation · 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).

FieldTypeNotes
productIdID
variantIdID
startDateStringFirst 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.
endDateStringLast day, Y-m-d.
slimBooleanA lighter calendar payload (no day prices).
displayStringglobal: the calendar as the store-wide calendar shows it.
locationIdIDThe pick-up location: the calendar is for that location’s stock and bookings.
methodRentalHandoverMethodThe calendar for this method (its transit and hand-over days; the payload’s handover).
returnMethodRentalReturnMethod

RentalPriceInput – What to price. Give variantId, or productId when the product has one rent variant.

FieldTypeNotes
productIdID
variantIdID
quantityInt = 1
startDateString!Start date (Y-m-d), or date and time (2026-10-01T09:00).
endDateStringEnd date; default the start date (one day).
startTimeStringStart time (HH:mm) when startDate has none.
endTimeStringEnd time (HH:mm) when endDate has none.
guestsRentalPartyInputThe party, for a product that counts guests.
localeStringThe storefront language for texts such as the Rental dates attribute, e.g. fr.
locationIdIDThe pick-up location (prices don’t depend on it yet).
methodRentalHandoverMethodThe method (prices don’t depend on it yet).
returnMethodRentalReturnMethod

RentalPartyInput – A party of guests, for products that count guests. Leave it out for products that don’t.

FieldTypeNotes
guestsIntTotal guests, when the product doesn’t split them.
adultsInt
childrenInt
infantsInt
petsInt

RentalDayPricesInput

FieldTypeNotes
productIdID
variantIdID
startDateStringFirst day, Y-m-d; default today.
endDateStringLast 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).

FieldTypeNotes
fromStringFirst day, Y-m-d.
toStringLast day, Y-m-d (default from).
fromTimeStringHH:mm
toTimeStringHH:mm
quantityInt
lengthStringA length code instead of to: 4h, 3d, 2w, 1m, 1y.
guestsInt
adultsInt
childrenInt
infantsInt
petsInt
whereStringA place as typed (≤ 100 characters): the shop’s own places first, then the geocoder.
radiusKmIntKilometres around where, 1–500 (default 25).
locationIdIDA pick-up location (the theme’s sr_loc): available THERE; without it, available at some location.

RentalHandoverWindowsInput

FieldTypeNotes
productIdID
variantIdID
methodRentalHandoverMethod
returnMethodRentalReturnMethod
dateString!The day (Y-m-d).
locationIdID
backBooleanThe return day’s slots instead of the out day’s.

RentalReserveInput – What to hold: the same as the price input.

FieldTypeNotes
productIdID
variantIdID
quantityInt = 1
startDateString!Start date (Y-m-d), or date and time (2026-10-01T09:00).
endDateStringEnd date; default the start date (one day).
startTimeStringStart time (HH:mm) when startDate has none.
endTimeStringEnd time (HH:mm) when endDate has none.
guestsRentalPartyInputThe party, for a product that counts guests.
localeStringThe storefront language for the Rental dates text, e.g. fr.
locationIdIDWhere the customer picks it up; required when the product is at two or more locations (location_required).
paymentRentalPaymentChoice = FULLPART 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.
methodRentalHandoverMethodHow it reaches the customer; required when the product offers two or more (method_required).
returnMethodRentalReturnMethod
windowStringThe out time slot (08:00-10:00) when the method has slots.
backWindowString
EnumValues
RentalHandoverMethodPICKUP – The customer picks it up.
DELIVERY – The store delivers it.
SHIPPED – It is posted (shipped).
RentalReturnMethodIN_STORE – The customer brings it back to the store.
COLLECT – The store collects it.
POST – The customer posts it back.
RentalPaymentChoiceFULL
PART

And their types. Fields marked “secret key only” are refused with a publishable key:

TypeFields
RentalShop
The store the credential belongs to.
domain: String!, name: String, currency: String!, timezone: String!, today: String!, primaryLocale: String, checkoutReady: Boolean!
RentalLocation
A 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
RentalLocationAddressaddress1: String, address2: String, city: String, province: String, zip: String, country: String, countryCode: String, phone: String (secret key only)
RentalCalendar
Availability 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!
RentalDateWindow
A range of days, Y-m-d.
from: String!, to: String!
RentalBookedDay
A day with every unit taken.
date: String!, quantity: Int!
RentalInventoryInterval
Units taken over an interval, turnaround included.
quantity: Int!, start: String!, end: String!
RentalProduct
A 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!
RentalPriceList
The 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
RentalPriceFromamount: Float!, unit: String
RentalPriceHeadlineamount: Float!, period: String
RentalRate
One period of the rate card.
label: String!, period: String!, quantity: Int!, unit: String!, days: Float!, price: Float!, perDay: Float, addOn: RentalRateAddOn
RentalRateAddOnlabel: String!, period: String!, price: Float!
RentalQuote
What 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
RentalQuoteLine
One line of a price breakdown.
label: String!, kind: String!, amount: Float!
RentalPaymentOptions
How 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
RentalPaymentSplit
Part now, the balance later: decimal strings in the store currency.
payNow: String!, balance: String!, dueDate: String!, dueAt: String, dueLabel: String, percent: Float, text: String
RentalDayPrices
A 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
RentalDayPricedate: String!, amount: Float!
RentalCatalogAvailabilityitems: [RentalCatalogEntry!]!, where: RentalWhere
RentalCatalogEntrykey: String!, productId: ID, handle: String, rental: Boolean!, available: Boolean, units: Int, distanceKm: Float, near: Boolean
RentalWhere
A place a Where search resolved to.
resolved: Boolean!, label: String, lat: Float, lng: Float, radiusKm: Int
RentalMapmarkers: [RentalMapMarker!]!, where: RentalWhere
RentalMapMarkerproductId: 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
RentalHandoverWindow
A time slot of a hand-over day.
id: String!, from: String!, to: String!, label: String, capacity: Int, left: Int, full: Boolean!
RentalKitid: ID!, legacyResourceId: Int!, productId: ID!, variantId: ID!, title: String!, source: RentalKitSource!, pricing: RentalKitPricing!, itemsDiscountPercent: String, useItemRules: Boolean!, items: [RentalKitItem!]!
RentalKitItemproductId: ID!, variantId: ID!, title: String!, variantTitle: String, quantity: Int!
RentalReservePayloadtoken: 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
RentalAttribute
A cart line attribute, ready for the Storefront API’s CartLineInput.attributes.
key: String!, value: String!
RentalHold
Dates held for a shopper until checkout (or until it expires).
id: ID!, legacyResourceId: String!, expiresAt: String!
RentalReservePayment
How a held rental will be paid (what the token carries).
choice: RentalPaymentChoice!, part: RentalPaymentSplit, reason: String, message: String
RentalHandover
A 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)
RentalReleasePayloadreleased: Boolean!, userErrors: [RentalUserError!]!
EnumValues
RentalKitSourceAPP – Made in the app or through this API.
SHOPIFY_BUNDLES – A Shopify Bundles product run as a fixed-price kit.
RentalKitPricingOWN – The kit’s own rental rates.
ITEMS_SUM – The sum of the items’ rental prices for the dates, minus itemsDiscountPercent.

Limits #

WhatLimitAnswer
Requests a minute, per store and shopper IP addressreserve 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 address10; release or the 30-minute expiry frees one429 too_many_holds
A hold30 minutes; the stock stays taken until then
Online booking not set up yetreserve refuses, so no line can check out at the wrong price503 not_ready with Retry-After
Keys and websites10 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.