Calenzy API
APIs/For your website

Bookings API

New

Stays, parking and rentals booked on your own website, and paid on your own Stripe account.

/v1/bookingsNot counted in your quota5 endpointsFrom your server only

The Bookings module of your dashboard, on your own website. What you rent, seasons, prices, extras, closed dates and the questions you ask all come from your Bookings settings. Every booking lands in your Bookings calendar, and the guest in your Clients.

Every call names the project that has Bookings, with ?project=.

How a booking works

  1. Show the calendar with Setup: your rules, prices and what is already taken. Prices follow the same rules as the dashboard, so the page can show them as the guest picks dates, or ask Price a stay.
  2. Take the booking with Create a booking. We check the dates again, price the stay again from your settings (never from what the page showed), record the guest and hold the dates for your hold time.
  3. Send the guest to pay at pay_url: Stripe Checkout on your own Stripe account, for what is due now (your reservation fee, plus extras charged at booking). When nothing is due now, the booking is confirmed at once and there is no pay_url.
  4. Show the result. Stripe sends the guest back to your return_url with ?booking={uid}. Ask Read a booking: we check the payment with Stripe and answer confirmed once it went through. A guest who leaves Checkout lands on your cancel_url: give the dates back with Release dates.
  5. We confirm it even if the guest never comes back, through Stripe's notice to our webhook (see Stripe setup). The guest then gets your confirmation email and you get a New booking email, both from the address set in your Bookings settings.
Online payment comes with the Pro and Premium plans and needs your Stripe key in the dashboard. Without it, a booking with something due now is refused with payment_unavailable, so no guest ever holds dates they cannot pay for.

Test mode

Add mode=test to Setup, Price a stay and Create a booking. Checkout then uses your Stripe test keys (saved in the dashboard next to the live ones), so you can pay with Stripe's test cards. A test booking is a real booking in your calendar, marked Test booking in its notes and "test": true here. Cancel it in the dashboard when you are done.

Stripe setup

Payments go to your own Stripe account, with the keys saved in your dashboard. In Stripe (Developers, Webhooks), add one endpoint, and a second one in test mode if you test:

  • Address: https://api.calenzy.app/webhooks/bookings/
  • Events: checkout.session.completed and checkout.session.async_payment_succeeded, for payment methods that settle later, like PromptPay
  • Save its signing secret in the dashboard with your Stripe key. Without it, a booking is still confirmed when the guest comes back to your return page, but not when they close the tab first.
GET/v1/bookings/setup

Setup

Everything the booking page shows, in one answer.

Query
projectRequiredintegerThe project that has Bookings
modestringtest for test mode. It decides online_payment.
Response
openbooleantrue when a booking can be taken. Show the page anyway, but take no booking while it is false
reasonstringok, or why it is closed: draft (Bookings not published in the dashboard), no_units, no_rates
booking_typestringaccommodation, parking or rental: tells you the words to use (night or day, guest or client)
currency, timezonestringFrom your Bookings settings. Dates are counted in this timezone
min_nightsintegerThe shortest stay you take
reservation_feenumberWhat is paid at booking
fee_modestringdeduct: the fee is part of the total and the rest is paid on arrival. add: the fee comes on top
season_rulestringHow a stay that crosses seasons is priced
hold_minutesintegerHow long dates stay held while the guest pays. Your setting, and at least 31 with online payment, since Stripe's payment page stays open 30 minutes
online_paymentbooleanWhether a booking with something due now can be paid (plan and Stripe key)
windowobjectfrom today to 18 months ahead: the period taken covers
unit_types[]arrayWhat can be booked: id, code, name, capacity (most people, or null), units_total
unit_types[].takenarrayRanges already booked or closed: from, to (exclusive), units
unit_types[].full_datesarrayNights with nothing left, ready to strike out in a date picker
seasons[]arrayid, code, name, the yearly range (from_month, from_day, to_month, to_day, which may wrap the year end) and is_default. The default season covers every date no other season does, so its range is null
rates[]arrayPrices per unit_type, season and length of stay: nights is a bracket, price its price
addons[]arrayExtras: id, code, name, price, price_unit (stay, night, week), charge_when (now or arrival)
questions[]arrayWhat you ask the guest: key, label (per language), input_type, options, required
Request
GETapi.calenzy.app/v1/bookings/setup?project=7
Response · 200 OK
{
  "status": "ok",
  "open": true,
  "reason": "ok",
  "booking_type": "accommodation",
  "currency": "THB",
  "timezone": "Asia/Bangkok",
  "min_nights": 5,
  "reservation_fee": 3000.0,
  "fee_mode": "deduct",
  "season_rule": "lowest",
  "hold_minutes": 31,
  "online_payment": true,
  "window": { "from": "2026-10-08", "to": "2028-04-08" },
  "unit_types": [
    {
      "id": 1, "code": "bungalow", "name": "Bungalow", "capacity": 2, "units_total": 4,
      "taken": [ { "from": "2026-12-01", "to": "2027-03-01", "units": 1 } ],
      "full_dates": []
    }
  ],
  "seasons": [
    { "id": 1, "code": "high", "name": "High season", "from_month": 10, "from_day": 23, "to_month": 4, "to_day": 18, "is_default": false },
    { "id": 2, "code": "green", "name": "Green season", "from_month": null, "from_day": null, "to_month": null, "to_day": null, "is_default": true }
  ],
  "rates": [ { "unit_type": 1, "season": 1, "nights": 7, "price": 21000.0 } ],
  "addons": [ { "id": 1, "code": "comfort", "name": "Comfort Add-On", "price": 3500.0, "price_unit": "week", "charge_when": "arrival" } ],
  "questions": [ { "key": "why_kohmak", "label": { "en": "Why Koh Mak?" }, "input_type": "textarea", "options": null, "required": false } ]
}
POST/v1/bookings/quote

Price a stay

The price of a stay, and whether the dates are free.

Query
projectRequiredintegerThe project that has Bookings
modestringtest for test mode
Body
checkin, checkoutRequiredstringYYYY-MM-DD. Check-out is the day the guest leaves
unit_typeinteger or stringAn id or code from Setup. The first one when absent
addonsarray or stringExtras by id or code: a list, a comma separated string, or "all"
Response
availablebooleanWhether the dates are free
quote.nightsintegerLength of the stay
quote.seasonstringThe season code the stay is priced in
quote.mixed_seasonbooleantrue when the stay crosses seasons
quote.accommodationnumberThe stay alone
quote.addons[]arrayEach extra: id, code, name, quantity, unit_price, amount
quote.addons_totalnumberAll extras
quote.totalnumberStay and extras
quote.due_now, quote.due_arrivalnumberWhat is paid at booking, and what is left for arrival
Request
POSTapi.calenzy.app/v1/bookings/quote?project=7
{
  "checkin": "2026-11-17",
  "checkout": "2026-12-17",
  "addons": ["comfort"]
}
Response · 200 OK
{
  "status": "ok",
  "available": true,
  "quote": {
    "checkin": "2026-11-17", "checkout": "2026-12-17", "nights": 30, "unit_type": 1,
    "currency": "THB", "season": "high", "mixed_season": false,
    "accommodation": 72000.0,
    "addons_total": 17500.0,
    "addons": [ { "id": 1, "code": "comfort", "name": "Comfort Add-On", "quantity": 5, "unit_price": 3500.0, "amount": 17500.0 } ],
    "total": 89500.0,
    "due_now": 3000.0,
    "due_arrival": 86500.0
  }
}
POST/v1/bookings/create

Create a booking

Hold the dates, record the guest, and get the payment page.

Query
projectRequiredintegerThe project that has Bookings
modestringtest for test mode
Body
checkin, checkoutRequiredstringAs for Price a stay
unit_type, addonsAs for Price a stay
first_name, last_name, emailRequiredstringThe guest
phonestringAs the guest typed it
party_sizeintegerHow many people, 1 when absent. Refused above the unit type's capacity
languagestringThe language the guest booked in (en, fr, de, zh-tw): their confirmation email and Checkout use it
arrival_time, arrival_travelstringHH:MM, and how they travel. Shown in your New booking email
answersobjectAnswers to your questions, {"question_key": "answer"}. Required when a question is
return_url, cancel_urlstringhttps pages on your website. We add booking={uid} to both. Required when something is due now
guest_ipstringThe guest's IP address, kept with the booking
Response
bookingobjectThe booking as Read a booking shows it. status is hold until paid, or confirmed when nothing was due now
pay_urlstring or nullStripe Checkout, to send the guest to now. null when nothing is due
expires_ininteger or nullSeconds the dates stay held for payment
quoteobjectThe price charged, as for Price a stay
Request
POSTapi.calenzy.app/v1/bookings/create?project=7
{
  "checkin": "2026-11-17",
  "checkout": "2026-11-24",
  "party_size": 2,
  "first_name": "Anna",
  "last_name": "Schmidt",
  "email": "anna@example.com",
  "phone": "+49 151 2345678",
  "language": "de",
  "arrival_time": "14:30",
  "answers": { "why_kohmak": "Quiet, the sea, and time to work." },
  "return_url": "https://www.example.com/book/done",
  "cancel_url": "https://www.example.com/book/cancelled"
}
Response · 200 OK
{
  "status": "ok",
  "booking": {
    "ref": "CP-7KQ2MX",
    "uid": "3f9c1e7a5b2d48c6a0e4f81b29d7c635",
    "status": "hold",
    "test": false,
    "checkin": "2026-11-17", "checkout": "2026-11-24", "nights": 7, "party_size": 2,
    "currency": "THB", "total": 21000.0, "due_now": 3000.0, "due_arrival": 18000.0, "paid": 0.0
  },
  "pay_url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "expires_in": 1860,
  "quote": { "nights": 7, "total": 21000.0, "due_now": 3000.0, "due_arrival": 18000.0 }
}
GET/v1/bookings/{uid}

Read a booking

One booking, with its payment checked.

Use the uid that Stripe adds to your return address, never the reference: the reference is short so a guest can read it out, the uid cannot be guessed. A booking still held with a Checkout is checked with Stripe first, and confirmed when the payment went through.

Path
uidRequiredstringThe booking identifier
Query
projectRequiredintegerThe project that has Bookings
Response
booking.ref, booking.uidstringThe reference the guest quotes, and the identifier for the API
booking.statusstringhold (waiting for payment), expired (a hold whose time ran out), pending, confirmed, arrived, cancelled, no_show
booking.testbooleanTaken in test mode
booking.checkin, checkout, nights, party_sizeThe stay
booking.unit_typeobjectid and name
booking.currency, total, due_now, due_arrival, paidThe money
booking.addons[]arrayname, quantity, amount
booking.guestobjectfirst_name, last_name, email, phone
booking.language, arrival_time, created_atstringAs recorded. created_at is in the project's timezone
Request
GETapi.calenzy.app/v1/bookings/3f9c1e7a5b2d48c6a0e4f81b29d7c635?project=7
Response · 200 OK
{
  "status": "ok",
  "booking": {
    "ref": "CP-7KQ2MX",
    "uid": "3f9c1e7a5b2d48c6a0e4f81b29d7c635",
    "status": "confirmed",
    "test": false,
    "checkin": "2026-11-17", "checkout": "2026-11-24", "nights": 7, "party_size": 2,
    "unit_type": { "id": 1, "name": "Bungalow" },
    "language": "de",
    "currency": "THB", "total": 21000.0, "due_now": 3000.0, "due_arrival": 18000.0, "paid": 3000.0,
    "addons": [],
    "guest": { "first_name": "Anna", "last_name": "Schmidt", "email": "anna@example.com", "phone": "+49 151 2345678" },
    "arrival_time": "14:30",
    "created_at": "2026-10-09 15:42:10"
  }
}
POST/v1/bookings/{uid}/release

Release dates

Give back the dates of a guest who left Checkout.

Call it from your cancel_url page. We close the guest's Stripe page first, so nobody can pay for dates that went back. Only an unpaid hold is released, and it becomes cancelled. A booking paid in another tab is confirmed instead.

Path
uidRequiredstringThe booking identifier
Query
projectRequiredintegerThe project that has Bookings
Response
releasedbooleantrue when the dates went back
bookingobjectThe booking, as Read a booking shows it
Request
POSTapi.calenzy.app/v1/bookings/3f9c1e7a5b2d48c6a0e4f81b29d7c635/release?project=7
Response · 200 OK
{
  "status": "ok",
  "released": true,
  "booking": {
    "ref": "CP-7KQ2MX",
    "uid": "3f9c1e7a5b2d48c6a0e4f81b29d7c635",
    "status": "cancelled",
    "paid": 0.0
  }
}

Errors

A refused call takes no booking: every check runs before anything is written, and if Stripe fails once the dates are held, we cancel the hold at once. Besides these, any call can answer one of the common errors.

CodeHTTPWhen
missing_project400No ?project=
invalid_dates400Not YYYY-MM-DD, or check-out not after check-in
past_dates400The check-in date has passed
stay_too_short400Fewer nights than min_nights
invalid_unit_type400A unit type that is not in this project
invalid_addon400An extra that is not in this project
invalid_guest400First name, last name or a valid email missing
party_too_large400More people than the unit type's capacity
missing_answer400A required question was not answered
invalid_url400return_url or cancel_url missing, or not https
bookings_not_configured404This project has no Bookings
booking_not_found404No booking with this uid in this project
bookings_closed409Bookings is not published, or has no units or prices (see reason in Setup)
dates_unavailable409The dates were taken in the meantime
unpriceable409No price is set for this stay
save_failed500The booking could not be saved. Try again
payment_unavailable503Something is due now but online payment is not available (plan, Stripe key, or Stripe did not answer). No booking was taken
bookings_unavailable503A moment's problem on our side. Try again shortly