- Bookings
- Availability
- Pricing
- Orders with rentals
- Balances (pay part now, rest later)
- Calendar sync (iCal)
- GET /api/v1/ical/products/{productId}
- POST /api/v1/ical/products/{productId}/feeds/rotate
- POST /api/v1/ical/products/{productId}/feeds/operational
- GET /api/v1/ical/imports
- POST /api/v1/ical/products/{productId}/imports
- PUT or PATCH /api/v1/ical/imports/{id}
- DELETE /api/v1/ical/imports/{id}
- POST /api/v1/ical/imports/{id}/sync
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.
| Field | Notes |
|---|---|
id | The booking number. |
product_id, variant_id, product_name, variant_title | The rent variant. |
order_id, order_name, line_item_id | The Shopify order and line (null for blocked dates and bookings still on a draft order). |
draft_order_id | The draft order a booking made in the app or the API waits on. |
quantity, quantity_sent, quantity_returned | Units booked, checked out and checked in. |
start, end | The customer’s dates, Y-m-d H:i:s in the store’s time zone. |
start_with_buffer, end_with_buffer | The same with the turnaround time added: what blocks the calendar. |
status | reserved, out, returned, cancelled or conflict (the order came in when the stock was gone). |
state | From the dates and the moves: upcoming, out, overdue or returned. |
is_booked | Still holds stock (everything but cancelled). |
source | online (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_at | guests is the party for products that count guests. |
location_id, location | The pick-up location, {id, name}; null means the store’s default location. |
payment | For rentals paid in part at checkout: {mode, full, now, balance, due, due_label, status, via}; else null. |
handover | For stores with delivery & pickup: {method, return_method, label, out_at, out_window, back_at, back_window, text, …}; else null. |
kind | item, 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).
| Parameter | Notes |
|---|---|
product_id, variant_id, order_id | Only this product, variant or order. |
state | upcoming, out, overdue or returned. |
status | One or more statuses, comma-separated: reserved,out. |
source | One or more sources, comma-separated. |
after | Only bookings that end (turnaround included) after this date or time. |
before | Only bookings that start (turnaround included) before this date or time; a bare date means that day at 23:59. |
search | An order name or number, a booking number, the customer, a product or variant title, a SKU or text in the notes. |
is_booked | true: still holding stock; false: cancelled. |
location_id | Picked up at this location. |
method, out_on, back_on | Delivery & pickup: pickup, delivery or shipped; goes out or comes back on this day (Y-m-d). |
orderby, order | id (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 field | Notes |
|---|---|
variant_id or product_id | Required. product_id works when the product has one rent variant. |
start, end | Required. 2026-12-01, or with a time: 2026-12-01 09:00. |
quantity | 1 to 10,000; default 1. |
customer_id or email | The customer the draft order is for. |
notes | Up to 2,000 characters. |
block | true to block dates instead of booking. |
force | With block: block the dates even over other bookings. |
location_id | The pick-up location, for stores that rent from several. |
method, return_method, window, back_window | Delivery & 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 field | Notes |
|---|---|
start, end | New dates; send one to keep the other. |
notes | Replaces the booking’s notes. |
force | Skip 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.
| Parameter | Notes |
|---|---|
variant_id or product_id | Required. |
start, end | Required. Dates, or dates and times. |
quantity | Default 1. |
exclude_booking_id, exclude_order_id | Leave this booking or order out (can a booking move to these dates?). |
apply_buffer | Default true: add the turnaround time around the range. |
location_id | Units 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.
| Parameter | Notes |
|---|---|
variant_id or product_id | Required. |
start, end | Required: Y-m-d, optionally with a time (Y-m-d H:i). |
quantity | Default 1. |
location_id | Checked 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.
| Parameter | Notes |
|---|---|
start, end | The window. Default: today, from 00:00 to the end of the day. |
product_id, variant_id | Only orders with this product or variant. |
state, status | Booking state, and booking statuses (comma-separated). |
search | An order name or number, a booking number, the customer, a title or a SKU. |
is_booked | Default true: only rentals that hold stock; false also lists orders whose rentals were cancelled. |
order | asc (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.
| Field | Notes |
|---|---|
id, order_id, order_name, customer_id, booking_ids | |
amount, currency | The balance, before tax, in the currency the customer pays in (a decimal string). |
amount_shop, shop_currency | The same in the store currency. |
due_date | Y-m-d H:i, store time. |
status | due, overdue, paid, waived, cancelled (the order was cancelled), failed (Shopify refused the change and the invoice) or adding. |
method | edit (on the order), invoice (a separate invoice) or pos. |
reminders_sent, paid_at | |
payment_url | The payment link: in GET /balances/{id} for a key with write access only. Keep it private. |
GET /api/v1/balances #
| Parameter | Notes |
|---|---|
status | open (default: due and overdue), due, overdue, paid, waived, cancelled, failed, adding or all. |
due_before | Due 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 field | Notes |
|---|---|
url | Required: the calendar’s .ics address. |
name | A name for the list. |
variant_id | The rent variant it blocks; required when the product has several. |
block_quantity | Units each event blocks; default every unit. |
interval_minutes | Minutes between reads: at least 5, default 30. |
sync_now | Default 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}.
