docs
/
AI skills

Booking type and availability

Set up something people can book (its services, lengths, prices, staff, days, hours, time zone, breaks and how many at once), see exactly which times it offers, put it on the site's booking page, and know what is sent when a booking is made, changed or cancelled.

A booking type is a reservation_definition. A booking is a reservation made against it. Times are never stored: the engine works out the open slots each time someone asks, from the booking type's days, hours, services, breaks and the bookings already made.

Related jobs: chairs, rooms or tables are Bookable resources for a booking type; a booking form on your own page is AppMint JS engine on a page; an assistant that books for callers or chatters is AI assistant for chat or phone.

Ask first

  • What is booked: each service's name, length in minutes, price, and any gap needed after it (cleanup, notes).
  • When: the days, the opening and closing time, the time zone, and any daily break (lunch).
  • How many at once: how many bookings one time can take.
  • With whom: the staff who deliver it, and whether a booking goes to one named person, rotates, or stays unassigned for the team.
  • Confirm automatically, or does staff confirm each new booking?
  • Who else should hear of each booking: extra email addresses and phone numbers.
  • Reminders: the defaults (below), or their own times.
  • Where people book: the site's booking page, a page of their own, or only staff and the AI.

Steps

1. Reservations: the booking type

Look first: find_records on reservation_definition. Then create_record (datatype: "reservation_definition") or update_record_partial (keys data.<field>). In the Studio: Reservations › Reservation Definitions (/reservations?tab=reservation-definitions). Its form asks the main questions; services, blocked times and reminders are under All settings.

FieldValueNotes
namee.g. consultationUnique key: letters, digits, -, _.
title, description, imageshown to the person booking
typeservice (appointments)service_point for chairs/rooms/tables, see the resources skill.
statusactive or inactiveinactive offers no times and refuses bookings.
services[{ name, duration, price, breakAfter, description, images }]duration and breakAfter in minutes. Leave the field out (not []) for a single unnamed service; then the booking type's own duration is the length (default 30).
workDays["Monday","Tuesday",…]Full English names, capitalised. "monday" closes that day.
officeHours{ timezone: "America/Chicago", startTime: "09:00", endTime: "17:00" }One set of hours for every open day. The last slot must end by endTime. Without it: UTC, 00:00 to 23:59.
blockedTime[{ startTime: "12:00", endTime: "13:00" }]A daily break, in the booking type's time zone, on every open day. Not a date.
spotsnumber, default 1Bookings one time can take.
hostsstaff emailsWho delivers it.
bookingTypeunset, round-robin, unassignedUnset with one host: every booking is that host's, and the host can't be double-booked. round-robin: the next host after the last booking's who is free and not on leave. unassigned: no host.
autoConfirmtrue / falseA visitor's booking starts new; with autoConfirm it starts confirmed. A booking staff make is always confirmed.
maxPartySizenumber or emptyA bigger party is refused (party_too_large).
checkInByminutesHow long a late booking is held, for check-in.
emails, phonesextra addresses / numbersCopied on every booking notice and reminder.
notificationssee step 3The reminders.
meetingLink, venue, meetingInfoPut in the confirmation.
businessLocationIda business location's skBookings belong to that location.
paymentRequiredleave offSee the warning in step 4.
forma form's nameExtra questions on the booking page.

Everything on a booking type is public: GET /crm/reservations/definitions and the slots call return the whole record (hosts, emails, phones, reminder texts) to anyone. Put nothing private in it.

Hands on: the booking type's sk. Check: step 2, for the next open day.

2. Reservations: the times it offers

call_appengine POST /crm/reservations/slots (no sign-in needed) with:

{ "reservationDefinitionId": "<sk>", "serviceName": "Consult", "serviceDate": "2026-10-26" }
  • reservationDefinitionId must be the sk; the name answers 400 invalid id.
  • serviceName must match a service's name exactly, same case ("consult" answers 400 Service type "consult" not found.). Left out, it is the first service.
  • serviceDate is a calendar day in the booking type's time zone. date, appointmentDate, startDate, day work too. A past day answers 400 "Invalid Service Date, date must be in the future.". Today is allowed; slots already started are left out.
  • host: only that host's free times.

It answers { reservationDefinition, service, slots: [{ startTime, endTime, spotsAvailable, businessTimezone }] }. Times are UTC; show them in businessTimezone. A closed day answers { slots: [], closed: true, error: "We are closed on this day. Please pick Monday, …" }. Full slots are left out.

How the slots are made (reservations.service.ts 661–736, 781–809, 1055–1086, 1028–1052):

  1. The first slot starts at opening time.
  2. The next starts duration + breakAfter + gracePeriod minutes later. (A booking type with slotGeneration set to anything but duration steps by the request's slotIncrementMinutes instead, default 5.)
  3. A slot must end by closing time.
  4. A slot that overlaps a blockedTime is dropped; the next slot still starts on the step.
  5. A slot is full when spots live bookings overlap it, counting each booking's breakAfter. Cancelled bookings don't count.

Tested on 2026-10-20: Monday–Friday, 09:00–12:00 Chicago, break 10:30–11:00, spots: 2.

ServiceSlots offered
Consult, 30 min + 15 breakAfter9:00, 9:45, 11:15 (10:30 falls in the break)
Quick, 15 minevery 15 minutes from 9:00 to 10:15, then from 11:00 (10:30 and 10:45 fall in the break)
Any, on Saturdaynone, closed: true

For several days at once: POST /crm/reservations/availability with { reservationDefinitionId, serviceName, from: "YYYY-MM-DD", days: 7 } (up to 31). It answers nextAvailable and each day with closed, openSlots and its slots, full ones included (spotsAvailable: 0).

Hands on: the open times for one day, in the business's time zone. Check: they match what the person expects. If a day is empty, check workDays spelling, officeHours, blockedTime, status, and whether it is already past.

3. Reservations: what gets sent

Every booking sends real email and SMS (reservations.service.ts 1563–1632, 238–400, 1635–1840, 1919–2088):

WhenTemplate (email + SMS)To
Booking madenotificationTemplate, else reservation-confirmation, with a calendar invitethe customer, emails, the booking's host (or every host if none), and a copy to the business (setting notificationCopyTo.reservation.sendCopyTo, default the business email). SMS: the customer's phone and phones.
Remindersnotifications, else the defaults belowthe same people
Changed by staffnotificationTemplate, else reservation-update
Rescheduledreservation-rescheduled (skipped with notify: false)
CancelledcancellationTemplate, else reservation-cancelledthe same people; queued reminders are removed
Deletedreservation-deletethe customer

The default reminders, when notifications is empty: email and SMS 1 day before, the morning of (unit: "morning_of", 08:00), 1 hour before, and at the start. Each entry of your own is { timeBefore, unit: "minutes" | "hours" | "days", type: "email" | "sms" | "whatsapp" | "email,sms", subject, html, text }. A reminder whose time has already passed when the booking is made is skipped.

A reminder with timeBefore: 0 is sent the moment the booking is made, not at its time (reservations.service.ts 309–311). The defaults have two of those (the morning-of and the at-start ones), so a new booking with the defaults gets its confirmation plus two more emails and two more texts at once. Set your own notifications with timeBefore of 1 or more for every entry. The morning-of time is 08:00 on the engine's clock, not the business's.

To word these messages, see Email and SMS templates with merge fields.

Stop. Save notifications only after the person has seen the list of what each booking will send, to whom, and when.

Hands on: the reminder plan, written out. Check: read the booking type back; notifications is what was agreed.

4. Site Config: the booking page

The site's booking page is the Reservations feature: /book while it is on, 404 while off. To switch it on or move it to a page of their own, see Site page routing. Confirmation emails link to it.

What the page does (base-app components/booking/BookingRoute.tsx, modules/reservation-system):

  • Lists every active booking type, except one named meeting (the internal one). /book?definition=<sk> opens one straight on its calendar; use that link for a "Book now" button.
  • Steps: booking type (if several), service (if several), with whom (if several), date and time, the visitor's details. It books through POST /crm/reservations/book: no account needed, a name and an email or phone required. The visitor becomes a customer in CRM. One email or phone can make 3 bookings an hour from the site; the next is refused with 429 "Too many bookings for this contact. Try again later, or contact us directly." (reason: too_many_bookings). Staff bookings aren't counted (reservations.service.ts throttleSiteBooking).
  • /book?manage lets a visitor find a booking by email and confirmation code, then move or cancel it. The code is required: GET /crm/reservations/by-email/<email>/<code>. Without it, only the signed-in customer whose email or phone it is, or staff, get a list; anyone else gets 400 "Enter your reservation number to find your booking, or sign in." (tested through a site's /api).

Leave paymentRequired off. With it on and a service priced above 0, the page goes to a payment step whose payment is never started: it shows "Preparing payment…" for ever, and no booking is made (reservation-payment.tsx 55–80). Prices are shown, never charged. There are no deposits.

Stop. Switching the feature on publishes the page, and every booking from it sends the messages in step 3. Wait for a yes.

Hands on: the booking page's address, and a ?definition=<sk> link. Check: open the page. The booking type, its services and the times from step 2 show. Don't finish a booking (step 5).

5. Check without booking

Don't make a test booking with POST /crm/reservations/create, POST /crm/reservations/book, create_record on reservation, or by finishing the booking page: each one emails and texts the customer, the hosts and the business. The checks in steps 2 and 4 are enough. If a booking must be proven, ask first, and use a @test.local email and no phone.

create_record on reservation goes through the same booking service as create (slot check, customer, notices). A plain repository write of a reservation is refused (reservation should be created using CRMService api).

Hands on: nothing new.

6. Reservations: change, move or cancel a booking (when asked)

JobCallNotes
Find bookingsfind_records on reservation, or Reservations (/reservations, /reservations?openId=<sk>)data.name is the 8-letter confirmation code.
MovePOST /crm/reservations/reschedule/<sk> with { startTime, service?, reason?, notify? }Refused with 409 slot_unavailable and problems[] (past, closed, outside_hours, blocked, full, host_busy, host_on_leave). Staff may add override: true to place it anyway. Keeps a rescheduleHistory.
CancelPOST /crm/reservations/cancel/<sk> with { reason }Status cancelled, stamped with who and why, reminders removed, notice sent. The booking is kept.
Change status or detailsPOST /crm/reservations/update with the recordSends reservation-update. Status can't be set to used (that is check-in) or rescheduled (use Move).

Stop. Each of these tells the customer by email and text (a move can skip it with notify: false). Say so, and wait for a yes.

Don't cancel or move a booking with update_record_partial. It writes the record directly: no slot check, no notice, and the reminders for the old time are still sent.

Hands on: the booking's code and its new state. Check: read it back; for a cancel, status is cancelled and cancelledAt is set.

When it's done

Tell the person:

  • the booking type's name, services (length, price, gap), days, hours and time zone, daily breaks, and how many per slot;
  • the open times you checked, for a real day, in their time zone;
  • who is told of each booking, and the reminder plan (and if they kept the defaults, that two reminders go out at once);
  • the booking page's address and the ?definition=<sk> link, or that it is still off;
  • that prices are shown but not charged.

Open the booking type: /reservations?openDefinition=<sk>.

What it can't do yet

  • No payment or deposit on the booking page (step 4).
  • No minimum notice or booking window. Any open slot from the next minute onwards can be booked, months ahead.
  • One set of hours for all days. Different hours on Saturday need a second booking type.
  • No gap before a service, only after (breakAfter). No one-off closed dates: blockedTime repeats every day. Staff leave is honoured for hosts.
  • A booking made through the API is not checked against open days or hours. book and create check the service, breaks and capacity, but not workDays, officeHours or a past time. The booking page only offers real slots; a direct call can book outside them.
  • Reminders with timeBefore: 0 go out at booking time, and morning-of uses the engine's clock (step 3).
  • Slot service names are exact-case; booking accepts any case. Use the names exactly as saved.

Don'ts

  • Don't write workDays in lower case or short form.
  • Don't put a date in blockedTime; it is a daily time.
  • Don't turn on paymentRequired.
  • Don't make a test booking, from any API or the page, without a yes. It sends real email and SMS.
  • Don't cancel or move bookings with update_record_partial.
  • Don't put private notes, phone numbers or staff details on the booking type you don't want public.
  • Don't switch the booking page on without a yes.