Creating a booking
Booking through the API is three calls, and the middle one is the only that changes anything. The engine decides the price at every step, so the number you show a guest is the number the agency will invoice.
1. Price the stay
Section titled “1. Price the stay”POST /quote with the unit slug and the dates. This
is also your minimum-stay and availability check — it answers 422 MIN_STAY
below the minimum, and available: false when the nights are taken.
const quote = await api("/quote", { method: "POST", body: { unit: "marina-loft-2br", arrival: "2026-09-10", departure: "2026-09-14" },});if (!quote.available) return offerSomethingElse();showGuest(quote.quote.total, quote.quote.currency);2. Create it
Section titled “2. Create it”POST /bookings with the
same dates and the guest’s contact details. No price travels with it — the
engine prices it again at the moment of writing, which is what keeps a quote
from going stale between the two calls.
const { booking } = await api("/bookings", { method: "POST", body: { unit: "marina-loft-2br", arrival: "2026-09-10", departure: "2026-09-14", adults: 2, guest: { firstName: "Nadia", lastName: "Rahman", email: "nadia@example.com" }, sourceRef: "your-order-8842", },});Put your own order id in sourceRef — it comes back on every read of that
booking and is how you reconcile later without keeping a mapping table.
Holds instead of firm bookings
Section titled “Holds instead of firm bookings”For a checkout flow that has not been paid yet, create the booking with
status: "option" and a holdHours. The nights are blocked, and the hold
releases itself if nobody confirms — no cleanup call needed from you. The agency
confirms it in the portal once payment lands.
3. Handle the refusals
Section titled “3. Handle the refusals”Four of them are worth distinct handling:
code | What happened | What to do |
|---|---|---|
MIN_STAY | Too short for these dates | Offer the minimum, it is in the response |
BOOKING_CONFLICT | Someone booked those nights first | Re-read availability and offer an alternative |
TURNOVER_BLOCK_CONFLICT | Cleaning block after the previous stay | Offer the date in conflict.bookableFrom |
EXPECTED_EXTENSION_CONFLICT | A guest in house is expected to stay longer | Offer another unit; this one is not really free |
The last two are the agency’s own rules, and the API deliberately gives you no way to override them. If a booking must happen anyway, that is a phone call to the agency, not a flag.
4. Cancelling
Section titled “4. Cancelling”POST /bookings/:id/cancel
releases the nights and keeps the record with your reason. Cancelling twice is
not an error, so a retry after a network timeout is safe:
await api(`/bookings/${booking.id}/cancel`, { method: "POST", body: { reason: "Payment failed" },});What a key cannot do
Section titled “What a key cannot do”Deliberate gaps, so you design around them rather than discover them:
- Set a price. Any price-shaped field is ignored; the engine decides.
- Edit a booking after the fact. Changing dates, moving units or adjusting the price go through the agency. Cancel and rebook if you must.
- Override a conflict. See above.
- Read or write identity documents. Passport data captured at check-in is encrypted and deleted on a schedule; it never leaves through the API.
Everything a key does write lands in the agency’s audit trail, attributed to that key — which is also why revoking a key never erases its history.