Skip to content

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.

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);

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.

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.

Four of them are worth distinct handling:

codeWhat happenedWhat to do
MIN_STAYToo short for these datesOffer the minimum, it is in the response
BOOKING_CONFLICTSomeone booked those nights firstRe-read availability and offer an alternative
TURNOVER_BLOCK_CONFLICTCleaning block after the previous stayOffer the date in conflict.bookableFrom
EXPECTED_EXTENSION_CONFLICTA guest in house is expected to stay longerOffer 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.

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" },
});

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.