View Categories

Launching soon on the Shopify App StoreJoin the waitlist

REST API reference: bookings and availability

14 min read

The REST API v1 of Rentals & Bookings, part 1: bookings, availability, pricing, orders with rentals, balances and calendar sync. Part 2 covers the Send & Return desk, serial numbers, stock, kits, delivery & pickup, rental settings and API keys: REST API reference: warehouse, stock and settings. Keys, limits, pagination and the error format are in the API overview.

Every endpoint on this page needs the Rentals & Bookings app. GET needs a read key; everything else needs a key with Allow changes (write). The examples use two shell variables:

API="https://YOUR-API-ADDRESS/api/v1"   # the REST address from Settings › API
KEY="sirb_your_secret_key"

Ids in the examples are made up: product 88001, variant 44001, order 5550001 (#1042), booking 42.

Bookings #

A booking is a number of units of one rent variant held for a date range: from an order, a draft order made in the app or through the API, blocked dates or an imported calendar.

FieldNotes
idThe booking number.
product_id, variant_id, product_name, variant_titleThe rent variant.
order_id, order_name, line_item_idThe Shopify order and line (null for blocked dates and bookings still on a draft order).
draft_order_idThe draft order a booking made in the app or the API waits on.
quantity, quantity_sent, quantity_returnedUnits booked, checked out and checked in.
start, endThe customer’s dates, Y-m-d H:i:s in the store’s time zone.
start_with_buffer, end_with_bufferThe same with the turnaround time added: what blocks the calendar.
statusreserved, out, returned, cancelled or conflict (the order came in when the stock was gone).
stateFrom the dates and the moves: upcoming, out, overdue or returned.
is_bookedStill holds stock (everything but cancelled).
sourceonline (checkout), pos, admin (made in the app or through this API), api, ical (an imported calendar), block (blocked dates) or import (imported from another system).
customer{id, name, email}.
notes, conflict_reason, guests, unit_price, currency, created_atguests is the party for products that count guests.
location_id, locationThe pick-up location, {id, name}; null means the store’s default location.
paymentFor rentals paid in part at checkout: {mode, full, now, balance, due, due_label, status, via}; else null.
handoverFor stores with delivery & pickup: {method, return_method, label, out_at, out_window, back_at, back_window, text, …}; else null.
kinditem, or kit for a rental kit, whose items lists one booking per item. An item of a kit has kit_booking_id.

GET /api/v1/bookings #

Lists bookings, newest first. Paged (page, per_page).

ParameterNotes
product_id, variant_id, order_idOnly this product, variant or order.
stateupcoming, out, overdue or returned.
statusOne or more statuses, comma-separated: reserved,out.
sourceOne or more sources, comma-separated.
afterOnly bookings that end (turnaround included) after this date or time.
beforeOnly bookings that start (turnaround included) before this date or time; a bare date means that day at 23:59.
searchAn order name or number, a booking number, the customer, a product or variant title, a SKU or text in the notes.
is_bookedtrue: still holding stock; false: cancelled.
location_idPicked up at this location.
method, out_on, back_onDelivery & pickup: pickup, delivery or shipped; goes out or comes back on this day (Y-m-d).
orderby, orderid (default), start or end; desc (default) or asc.
curl -s "$API/bookings?status=reserved,out&after=2026-12-01&orderby=start&order=asc&per_page=20" \
  -H "Authorization: Bearer $KEY"
# 200, X-Total: 37, X-Total-Pages: 2
[
  {
    "id": 42, "product_id": 88001, "variant_id": 44001, "product_name": "Party tent 6×12 m", "variant_title": null,
    "order_id": 5550001, "order_name": "#1042", "line_item_id": 13001, "draft_order_id": null,
    "quantity": 1, "start": "2026-12-04 00:00:00", "end": "2026-12-06 00:00:00",
    "start_with_buffer": "2026-12-04 00:00:00", "end_with_buffer": "2026-12-06 23:59:00",
    "quantity_sent": 0, "quantity_returned": 0, "is_booked": true, "notes": "",
    "state": "upcoming", "status": "reserved", "conflict_reason": null, "source": "online",
    "customer": {"id": 555001, "name": "Jordan Lee", "email": "[email protected]"},
    "guests": null, "unit_price": 450, "currency": "USD", "created_at": "2026-11-02T14:05:11+00:00",
    "location_id": null, "location": null, "payment": null, "handover": null, "kind": "item"
  }
]

GET /api/v1/bookings/{id} #

One booking. 404 not_found when the store has no such booking.

curl -s "$API/bookings/42" -H "Authorization: Bearer $KEY"
# 200: one booking, as in the list above

POST /api/v1/bookings #

Books a rental for a customer, or blocks dates. Without block, the store’s rules all apply (stock, closed days, lengths), the units are held for 7 days, and Shopify makes a draft order with the rental and its price: send the customer its invoice_url to pay. The booking’s source is admin and it becomes the order’s booking when the draft is paid. With block: true, the dates are blocked for maintenance or private use: no order, source: "block".

Body fieldNotes
variant_id or product_idRequired. product_id works when the product has one rent variant.
start, endRequired. 2026-12-01, or with a time: 2026-12-01 09:00.
quantity1 to 10,000; default 1.
customer_id or emailThe customer the draft order is for.
notesUp to 2,000 characters.
blocktrue to block dates instead of booking.
forceWith block: block the dates even over other bookings.
location_idThe pick-up location, for stores that rent from several.
method, return_method, window, back_windowDelivery & pickup: pickup, delivery or shipped; in_store, collect or post; time slots such as 08:00-10:00.
curl -s -X POST "$API/bookings" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"variant_id": 44001, "start": "2026-12-01", "end": "2026-12-03", "quantity": 1, "block": true, "notes": "Annual service"}'
# 201
{
  "booking": {"id": 43, "variant_id": 44001, "quantity": 1, "start": "2026-12-01 00:00:00", "end": "2026-12-03 00:00:00",
              "status": "reserved", "source": "block", "notes": "Annual service", "order_id": null, "…": "…"},
  "draft_order": null
}

Without block, draft_order is {"id": 1122334455, "name": "#D12", "invoice_url": "https://your-store.myshopify.com/…/invoices/…"}.

Refusals: 400 invalid_param (a missing or bad field), 400 not_supported (order_id: an order’s bookings come from Shopify’s checkout; apply_buffer: false: the turnaround time always applies), 404 invalid_product, 400 ambiguous_variant (data.variant_ids), 400 not_a_rental, 409 not_available (data.available), 409 with a rule code such as closed_day or min_length, 502 draft_order_failed.

PUT or PATCH /api/v1/bookings/{id} #

Moves a booking to new dates or changes its notes. The booking doesn’t compete with itself for stock; force: true skips the stock check. The quantity, product and order can’t change here: edit the order in Shopify.

Body fieldNotes
start, endNew dates; send one to keep the other.
notesReplaces the booking’s notes.
forceSkip the stock check.
curl -s -X PUT "$API/bookings/43" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"end": "2026-12-04", "notes": "Annual service, one extra day"}'
# 200: the booking, with its new dates

Refusals: 400 not_supported (quantity, product, order_id or is_booked), 409 imported (a booking from an imported calendar: change it there), 409 not_available, 409 rule codes.

DELETE /api/v1/bookings/{id} #

Deletes blocked dates, or a booking made through the API or an import that has no order. A booking still waiting on its draft order is cancelled instead (it stays, with status: "cancelled") and the open draft order is deleted in Shopify.

curl -s -X DELETE "$API/bookings/43" -H "Authorization: Bearer $KEY"
# 200
{"deleted": true, "previous": {"id": 43, "source": "block", "…": "…"}}
# a booking that waited on a draft order: {"deleted": true, "cancelled": true, "previous": {…}}

Refusals: 409 has_order (cancel or refund it in Shopify), 409 imported (remove it from the outside calendar), 409 not_deletable (made at checkout, at the counter or in the app), 409 draft_completed (the draft order was paid meanwhile), 409 has_draft_order.

GET /api/v1/bookings/{id}/history #

The booking’s check-outs (sends) and check-ins (returns), oldest first. user_id is the staff member at the desk; null for API moves.

curl -s "$API/bookings/42/history" -H "Authorization: Bearer $KEY"
# 200
{
  "sends":   [{"id": 1601, "datetime": "2026-12-04 08:12:40", "quantity": 1, "serials": ["TENT-003"], "user_id": 1, "notes": ""}],
  "returns": [{"id": 1655, "datetime": "2026-12-07 10:02:15", "quantity": 1, "serials": ["TENT-003"], "user_id": null, "notes": "Returned dry"}]
}

GET /api/v1/bookings/{id}/serials #

The serial numbers still out with the customer on this booking, in code order.

curl -s "$API/bookings/42/serials" -H "Authorization: Bearer $KEY"
# 200
["TENT-003"]

Checking a booking out and in (POST /bookings/{id}/send and /return) is part of the warehouse: see Send & Return. Changing its delivery or pickup (PATCH /bookings/{id}/handover) is under Delivery & pickup.

Availability #

GET /api/v1/availability #

Can this quantity be rented over these dates? The same numbers the storefront and the cart use: bookings and live holds, turnaround included, counted at the busiest moment of the range.

ParameterNotes
variant_id or product_idRequired.
start, endRequired. Dates, or dates and times.
quantityDefault 1.
exclude_booking_id, exclude_order_idLeave this booking or order out (can a booking move to these dates?).
apply_bufferDefault true: add the turnaround time around the range.
location_idUnits and bookings at this location only.
curl -s "$API/availability?variant_id=44001&start=2026-12-01&end=2026-12-03&quantity=2" \
  -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "variant_id": 44001,
  "start": "2026-12-01 00:00:00", "end": "2026-12-04 23:59:00",
  "max_quantity": 4, "booked": 1, "available": 3, "requested": 2, "needed": 2, "ok": true,
  "guests": null, "guest_errors": [], "location_id": null
}

start and end come back with the turnaround time added (for a date rental, the whole last day). ok is true when available covers the quantity, or when the product allows overbooking. This checks stock only: POST /bookings also checks the store’s rules (closed days, lengths).

GET /api/v1/availability/calendar #

The booked intervals and the fully booked days of one product, as the calendar sees them. Without start and end, everything from two days ago on.

curl -s "$API/availability/calendar?variant_id=44001&start=2026-12-01&end=2026-12-31" \
  -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "variant_id": 44001, "location_id": null, "max_quantity": 4,
  "inventory": [{"quantity": 1, "start": "2026-12-04 00:00", "end": "2026-12-06 23:59"}],
  "fully_booked_days": [{"date": "2026-12-24", "quantity": 4}]
}

Pricing #

Prices come from the same calculation as the storefront and the cart, in the store currency. Refusals: 404 invalid_product, 400 ambiguous_variant, 400 not_a_rental (rentals are off for the variant), 404 no_pricing (the product has no rental price).

GET /api/v1/pricing #

The rate card: every period the product is priced by, its price, and its price per day.

curl -s "$API/pricing?variant_id=44001" -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "variant_id": 44001, "name": "Party tent 6×12 m", "variant_title": "Default Title",
  "rental_type": "date", "price_type": "advanced",
  "from": {"amount": 450, "unit": "day"}, "headline": {"amount": 450, "period": "day"},
  "rates": [
    {"label": "1 day", "period": "1d", "quantity": 1, "unit": "day", "days": 1, "price": 450, "per_day": 450, "add_on": null},
    {"label": "1 week", "period": "1w", "quantity": 1, "unit": "week", "days": 7, "price": 1800, "per_day": 257.14, "add_on": null}
  ],
  "min_length": [], "max_length": [], "currency": "USD", "currency_symbol": "$"
}

GET /api/v1/pricing/quote #

What a rental over these dates costs, with the breakdown the product page shows: exactly what the cart will charge. It is a price, not a hold: check availability too.

ParameterNotes
variant_id or product_idRequired.
start, endRequired: Y-m-d, optionally with a time (Y-m-d H:i).
quantityDefault 1.
location_idChecked and returned; prices don’t depend on the location.
curl -s "$API/pricing/quote?variant_id=44001&start=2026-12-01&end=2026-12-03&quantity=2" \
  -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "variant_id": 44001, "start": "2026-12-01", "end": "2026-12-03 00:00:00",
  "quantity": 2, "days": 2, "hours": 48,
  "unit_price": {"amount": 900}, "total": {"amount": 1800},
  "lines": [{"label": "1 day × 2", "kind": "rate", "amount": 900}], "extra_lines": [],
  "guests": [], "length_unit": "day", "location_id": null, "payment": null,
  "currency": "USD", "currency_symbol": "$"
}

payment is filled when the store lets customers pay part now and the rest later: {options, default, part: {now, balance, due, due_at, due_label, percent, text}, reason, message}. Refusals: 400 invalid_dates, 400 no_price (data.reason: the dates can’t be priced).

GET /api/v1/pricing/days #

The price of a one-day rental starting on each day of a range: for a calendar that shows day prices. start defaults to today and end to 30 days later; at most 366 days (400 range_too_long).

curl -s "$API/pricing/days?variant_id=44001&start=2026-12-01&end=2026-12-03" -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "variant_id": 44001, "start": "2026-12-01", "end": "2026-12-03",
  "note": "The price of a one-day hire starting on each day.",
  "days": {"2026-12-01": {"amount": 450}, "2026-12-02": {"amount": 450}, "2026-12-03": {"amount": 450}},
  "currency": "USD", "currency_symbol": "$"
}

Orders with rentals #

GET /api/v1/orders #

The orders that have a rental running in a window (the rental dates with the turnaround time overlap it), each with every rental line of the order: the list to pick, pack and chase. Sorted by the earliest rental start and paged by order. Blocked dates and bookings still on a draft order have no order: find them with GET /bookings.

ParameterNotes
start, endThe window. Default: today, from 00:00 to the end of the day.
product_id, variant_idOnly orders with this product or variant.
state, statusBooking state, and booking statuses (comma-separated).
searchAn order name or number, a booking number, the customer, a title or a SKU.
is_bookedDefault true: only rentals that hold stock; false also lists orders whose rentals were cancelled.
orderasc (default) or desc, by the earliest rental start.
curl -s "$API/orders?start=2026-12-01&end=2026-12-07" -H "Authorization: Bearer $KEY"
# 200, X-Total: 9, X-Total-Pages: 1
[
  {
    "id": 5550001, "number": "#1042", "order_name": "#1042", "status": "reserved",
    "customer": {"id": 555001, "name": "Jordan Lee", "email": "[email protected]"},
    "earliest_start": "2026-12-04 00:00:00", "latest_end": "2026-12-06 00:00:00",
    "rentals": [
      {"booking_id": 42, "line_item_id": 13001, "product_id": 88001, "variant_id": 44001,
       "product_name": "Party tent 6×12 m", "variant_title": null, "sku": "TENT-6X12", "quantity": 1,
       "start": "2026-12-04 00:00:00", "end": "2026-12-06 00:00:00",
       "start_with_buffer": "2026-12-04 00:00:00", "end_with_buffer": "2026-12-06 23:59:00",
       "quantity_sent": 0, "quantity_returned": 0, "is_booked": true, "status": "reserved", "in_window": true}
    ]
  }
]

The order’s status comes from its lines: conflict, else out, else reserved, else returned, else cancelled. in_window says which lines overlap the window.

Balances (pay part now, rest later) #

When customers pay part of a rental at checkout, the rest (the balance) is added to the same order with a payment link, or to a separate invoice when the order can’t be changed. See Pay part now, rest later.

FieldNotes
id, order_id, order_name, customer_id, booking_ids
amount, currencyThe balance, before tax, in the currency the customer pays in (a decimal string).
amount_shop, shop_currencyThe same in the store currency.
due_dateY-m-d H:i, store time.
statusdue, overdue, paid, waived, cancelled (the order was cancelled), failed (Shopify refused the change and the invoice) or adding.
methodedit (on the order), invoice (a separate invoice) or pos.
reminders_sent, paid_at
payment_urlThe payment link: in GET /balances/{id} for a key with write access only. Keep it private.

GET /api/v1/balances #

ParameterNotes
statusopen (default: due and overdue), due, overdue, paid, waived, cancelled, failed, adding or all.
due_beforeDue on or before this day (Y-m-d).
curl -s "$API/balances?status=open" -H "Authorization: Bearer $KEY"
# 200, X-Total: 1
[{"id": 12, "order_id": 5550001, "order_name": "#1042", "customer_id": 555001, "amount": "675.00", "currency": "USD",
  "amount_shop": "675.00", "shop_currency": "USD", "due_date": "2026-11-27 09:00", "status": "due", "method": "edit",
  "reminders_sent": 0, "paid_at": null, "booking_ids": [42]}]

GET /api/v1/balances/{id} #

One balance, with payment_url for a key with write access.

POST /api/v1/balances/{id}/invoice #

Sends Shopify’s invoice email with the payment link. Body: to (another address), message (text in the email); both optional. 409 not_open once the balance is paid or waived.

curl -s -X POST "$API/balances/12/invoice" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"message": "The rest of your tent rental is due on Friday."}'
# 200: the balance

POST /api/v1/balances/{id}/mark-paid #

Records a payment made outside the checkout, such as cash. 409 outstanding_differs (data.outstanding) when the order owes more than the balance.

POST /api/v1/balances/{id}/waive #

Waives the balance: it comes off the order (or its invoice is deleted). Body: note (optional). Answers the balance.

Calendar sync (iCal) #

A rental product’s feeds are addresses other calendars subscribe to: the availability feed (busy or free, no customer data) for booking channels, and the bookings feed (every booking with its customer, off until you turn it on) for your team’s calendar. The calendars a product imports block its dates. See Calendar sync (iCal).

An import answers {id, product_id, name, host, variant_id, enabled, block_quantity, interval_minutes, last_sync_at, next_sync_at, last_status, last_error, last_event_count, failures, blocks}. Only the host of an imported address is shown: the full address carries the booking channel’s secret. A feed’s url is its own credential: anyone with it can read the feed, so rotate it if it leaks.

Refusals: 404 not_rentable (the product isn’t set up for rent), 404 not_found, 400 invalid (data.field: url, variant_id, block_quantity, interval_minutes or flavour), 400 too_many (20 calendars per product), 400 not_enabled (rotating the bookings feed while it is off). Imported addresses must be public http or https addresses.

GET /api/v1/ical/products/{productId} #

A product’s feeds, its rent variants and its imported calendars. The availability feed is made the first time you read it.

curl -s "$API/ical/products/88001" -H "Authorization: Bearer $KEY"
# 200
{
  "product_id": 88001, "rental_type": "date",
  "feeds": {
    "product_id": 88001,
    "availability": {"id": 2, "variant_id": 0, "flavour": "availability",
                     "url": "https://YOUR-API-ADDRESS/ical/4d92…ef4a.ics", "rotated_at": null, "last_access_at": null},
    "operational": null, "variants": []
  },
  "variants": [{"id": 44001, "title": "Party tent 6×12 m", "units": 4}],
  "imports": []
}

POST /api/v1/ical/products/{productId}/feeds/rotate #

Gives a feed a new address; the old one stops working at once. Body: flavour (availability, the default, or operational for the bookings feed), variant_id (optional). Answers the product’s calendar sync, as above.

POST /api/v1/ical/products/{productId}/feeds/operational #

Turns the bookings feed on or off. Body: enabled (true or false, required), variant_id (optional).

GET /api/v1/ical/imports #

The calendars the store imports. product_id narrows it to one product.

POST /api/v1/ical/products/{productId}/imports #

Imports another calendar (Airbnb, Booking.com, Google …): its events block the dates.

Body fieldNotes
urlRequired: the calendar’s .ics address.
nameA name for the list.
variant_idThe rent variant it blocks; required when the product has several.
block_quantityUnits each event blocks; default every unit.
interval_minutesMinutes between reads: at least 5, default 30.
sync_nowDefault true: read it at once.
curl -s -X POST "$API/ical/products/88001/imports" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://calendar.example.com/tent.ics", "name": "Booking channel"}'
# 201
{
  "result": {"status": "ok", "message": "…", "added": 3, "updated": 0, "removed": 0, "events": 3},
  "import": {"id": 5, "product_id": 88001, "name": "Booking channel", "host": "calendar.example.com", "variant_id": 44001,
             "enabled": true, "block_quantity": null, "interval_minutes": 30, "blocks": 3, "…": "…"},
  "product": {"…": "the product's calendar sync"}
}

PUT or PATCH /api/v1/ical/imports/{id} #

Changes an imported calendar; only the fields you send change: url, name, enabled, block_quantity (null: every unit again), interval_minutes. Answers {import, product}.

DELETE /api/v1/ical/imports/{id} #

Stops importing a calendar and frees the dates it blocked. Answers {"deleted": true, "id": 5, "product": {…}}.

POST /api/v1/ical/imports/{id}/sync #

Reads an imported calendar now. Answers {result, import}.