View Categories

Launching soon on the Shopify App StoreJoin the waitlist

REST API reference: bookings and availability

23 min read

The REST API v1 of Rentals & Bookings, part 1: bookings, availability, pricing, orders with rentals, balances, rentals billed per period (subscriptions) and calendar sync. Part 2 covers the Send & Return desk, serial numbers, stock by location and transfers, kits, delivery & pickup (areas, the postcode check and the day sheet), POS alerts, 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.
return_location_id, returned_elsewhereA one-way rental: the location it goes back to (null = back where it was picked up), and whether it came back to another location than planned.
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, zone, postcode, …}; else null. zone is the delivery area the booking was made for ({id, name}) and postcode the delivery postcode.
term, subscriptionFor a rental billed per period: term is o (until it comes back) or f (a fixed number of periods), and subscription is the subscription as below; both null otherwise.
nightly, nights, check_in, check_outA stay by the night: nightly is true, nights counts the nights and check_in / check_out are the two days (Y-m-d; start and end are the same days at 00:00). For a day rental: nightly false and the rest null. A booking keeps the rule it was sold under.
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, "return_location_id": null, "returned_elsewhere": false,
    "payment": null, "handover": null, "term": null, "subscription": null,
    "nightly": false, "nights": null, "check_in": null, "check_out": 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. For a product booked by the night, they are the check-in and check-out days: a booking needs at least one night (400 zero_nights otherwise); one day of blocked dates blocks that night.
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_id, return_location_idThe pick-up location, for stores that rent from several; and, for a one-way rental, the location it goes back to (the product’s return rules decide where that may be).
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, 400 zero_nights (a stay without a night), 404 location_not_found, 400 return_location_unavailable (the product can’t be returned there), 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. check_in and check_out stand for them, for stays by the night.
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, "nightly": false, "nights": 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).

A product booked by the night (Booked by: Nights) is checked over [check-in, check-out): end is the check-out day at 00:00, so the check-out day stays free for the next stay. The answer carries nightly: true and nights. A same-day or reversed pick is refused with 400 zero_nights.

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. location_id limits it to one location’s stock and bookings; check_in and check_out stand for start and end.

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}],
  "nightly": false, "turnover_dates": [], "days": []
}

nightly says whether the product is booked by the night. For a nightly product, turnover_dates lists the days one stay ends and another begins, and days has one row per day of the window (default today plus 89 days) for one unit: {date, state, check_in, check_out}. state is free, checkin-only (the night before is sold), checkout-only (this night is sold), turnover or booked; check_in is true when a stay may begin that day and check_out when one may end. For a day rental days is empty.

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). check_in and check_out stand for them, for stays by the night.
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", "nightly": false, "nights": null, "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).

Stays by the night. For a nightly product the price counts nights: nightly is true, nights is the number of nights, length_unit is night. A same-day or reversed pick is refused with 400 zero_nights. GET /pricing prints the rate card per night too (headline.period and each rate’s unit can be night), and GET /pricing/days gives the price of one night starting on each day.

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.

Subscriptions (rentals billed per period) #

A rental can be billed per period instead of for fixed dates: ongoing (until it comes back) or for a fixed term of a number of periods. The unit stays held until it is returned or the term ends. A subscription belongs to a booking (the booking’s term and subscription show it) and is billed one of two ways: invoice (the app sends a Shopify invoice with a payment link each period), or external (another app’s subscription, a selling plan on the rent variant: that app charges, the stock stays held here). contract is the app’s own subscription contract.

FieldNotes
idThe subscription number. booking_id, order_id, order_name, customer_id, product_id, variant_id and quantity tie it to the booking and the first order.
sourceexternal, invoice or contract. selling_plan_id and plan_name are set for external.
interval, interval_label, every_labelHow often it is billed: a code such as 1M or 2W, and its text.
term, cycles, cycles_paid, term_completeo (ongoing) or the number of periods; periods paid so far; true when a fixed term is fully paid.
period_price, currencyThe price of one period, a decimal string.
statusactive, past_due (a period wasn’t paid by its due date plus the store’s grace days), paused, ending (ended: due back by return_by), ended (every unit is back), cancelled or owned. overdue is true when past_due.
start, paid_through, next_due, hold_until, return_by, lateDates (Y-m-d): when it began, the last day paid for, when the next period is due, how long the unit is held, and when it must be back once ended. late is true when it is past return_by and not back.
ended_reason, ended_at, summaryWhy and when it ended, and a one-line text.
periodsOn GET /subscriptions/{id} only: [{id, cycle, start, end, due, status, via, amount, currency, order_id, order_name, draft_order_id, paid_at}]. status is due, paid, failed, skipped, waived, refunded or cancelled; via is first, renewal, invoice, staff or flow.

Customers start a subscription when they book (the subscription field of the storefront reserve, see the storefront API); the endpoints below manage it afterwards. GET needs a read key; the others need a key with write access.

GET /api/v1/subscriptions #

Lists subscriptions, past due first, then ending, then active, by next due date. Paged (page, per_page). Parameters: status (live, the default: active, past due, paused and ending; or active, past_due, ending, ended, cancelled, all; anything else is 400 invalid_param) and customer_id.

curl -s "$API/subscriptions?status=past_due" -H "Authorization: Bearer $KEY"
# 200, X-Total: 1 (trimmed; the list has no periods)
[
  {
    "id": 5, "booking_id": 61, "order_id": 5550003, "order_name": "#1044", "customer_id": 555001,
    "product_id": 88001, "variant_id": 44001, "quantity": 1, "source": "invoice", "selling_plan_id": null, "plan_name": null,
    "interval": "1M", "interval_label": "month", "every_label": "every month", "term": "o", "cycles": null, "cycles_paid": 2,
    "term_complete": false, "period_price": "45.00", "currency": "USD", "status": "past_due", "overdue": true,
    "start": "2026-10-01", "paid_through": "2026-11-30", "next_due": "2026-12-01", "hold_until": null, "return_by": null,
    "late": false, "ended_reason": null, "ended_at": null, "summary": "Ongoing since Oct 1, 2026 · 45.00 USD / month · paid through Nov 30, 2026"
  }
]

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

One subscription with its periods. 404 not_found when the store has no such subscription.

curl -s "$API/subscriptions/5" -H "Authorization: Bearer $KEY"
# 200: the subscription as above, plus
#   "periods": [{"id": 9, "cycle": 1, "start": "2026-10-01", "end": "2026-10-31", "due": "2026-10-01", "status": "paid", "via": "first",
#                "amount": "45.00", "currency": "USD", "order_id": 5550003, "order_name": "#1044", "draft_order_id": null,
#                "paid_at": "2026-10-01T09:12:40+00:00"}, …]

POST /api/v1/subscriptions/{id}/end #

Ends the rental: billing by invoice stops and the units are due back by return_by (default the end of the paid period). Body: return_by (Y-m-d, optional) and note (up to 500 characters). The status becomes ending; it turns ended when every unit is back. A subscription from another app must be cancelled in that app too. Answers the subscription. Refusals: 400 invalid_return_by, 404 not_found, 409 not_endable.

curl -s -X POST "$API/subscriptions/5/end" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"return_by": "2026-12-31", "note": "Customer asked"}'
# 200: the subscription, "status": "ending", "return_by": "2026-12-31"

POST /api/v1/subscriptions/{id}/payments #

Records a payment for the next unpaid period (cash, a bank transfer, a renewal order that wasn’t matched). Body: amount (optional, a number: the amount paid), note (up to 500 characters) and order_id (optional: the Shopify order that paid it). Answers the subscription. Refusals: 400 invalid_amount, 409 not_payable (the rental has ended).

curl -s -X POST "$API/subscriptions/6/payments" -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"amount": "45.00", "note": "Bank transfer"}'
# 200: the subscription, with "paid_through" and "next_due" moved on

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

Sends the invoice of the next period now (for a subscription billed by invoice): Shopify makes a draft order and emails the customer the payment link. No body. Answers the subscription. Refusals: 409 not_invoiced (billed by another app), 409 not_live (it has ended), 409 term_paid (every period is paid), 502 draft_order_failed.

curl -s -X POST "$API/subscriptions/5/invoice" -H "Authorization: Bearer $KEY"
# 200: the subscription

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, "location_id": null, "flavour": "availability",
                     "url": "https://YOUR-API-ADDRESS/ical/4d92…ef4a.ics", "rotated_at": null, "last_access_at": null},
    "operational": null, "variants": [], "locations": []
  },
  "variants": [{"id": 44001, "title": "Party tent 6×12 m", "units": 4}],
  "imports": []
}

For a product with stock at each location, feeds.locations has one entry per location that rents: {location_id, name, availability, operational}. A location’s availability feed shows the dates when that location has none free; its bookings feed (operational, present while the product’s bookings feed is on) lists the bookings picked up there. The list is empty for a store with one location or a product with one shared stock.

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) or location_id (that location’s feed). 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}.