DOM Package

Service Catalogs and Locked Booking

Load service catalogs, build calendar-first browsing, and embed booking for a selected service or category

Use the DOM Package to load the services connected to a customer flow without rendering the complete customer-flow interface. Your application can build its own catalog and service-detail pages, then embed only the booking journey for the selected service or category.

Service catalogs and locked booking require @tiquo/dom-package 1.6.3 or newer. Use 1.6.6 or newer for the complete typed field set documented on this page, including explicit service/category images, names, and descriptions.

Customer flow ID

The methods on this page use the customer flow's booking configuration ID. It is the ID in the flow's booking or embed URL.

For example, if the embed URL is:

https://book.tiquo.app/embed/CUSTOMER_FLOW_ID

pass CUSTOMER_FLOW_ID as bookingConfigId.

The customer flow must belong to the same organization as the Website SDK public key used to initialize the package. The current website domain must also be enabled under Settings > Website SDK.

Load a service catalog

Call getFlowServices() to load the active services connected to a customer flow:

const catalog = await tiquo.getFlowServices('CUSTOMER_FLOW_ID');

for (const service of catalog.services) {
  console.log(service.id);
  console.log(service.name);
  console.log(service.image);
  console.log(service.serviceImage);
  console.log(service.categoryImage);
  console.log(service.categoryName);
  console.log(service.categoryNames);
  console.log(service.categoryDescription);
  console.log(service.categoryDescriptions);
  console.log(service.effectivePrice);
}

Public services can be loaded without signing in. When the DOM Package has an authenticated customer session, the catalog also includes membership-gated services that the customer is allowed to book. Services that are inactive, outside their visibility window, or unavailable to the current customer are not returned.

Membership eligibility and service price are separate. A free or paid membership can grant permission to see or book a service, but the service remains paid unless a discount, store credit, or another pricing rule changes its effective price.

The catalog response contains:

FieldDescription
bookingConfigIdCustomer flow ID supplied to getFlowServices()
flowPublic flow information: id, name, and optional description
servicesServices available to the current visitor
categoriesFlow categories and the service IDs assigned to each category
catalogSectionsConfigured catalog sections with category and service IDs
currencyFlow location currency, when configured

Each category contains id, name, optional description, optional image, optional images, serviceIds, and optional presetSection. images is the category's gallery in display order (up to 15), and image is its first entry. Each catalog section contains id, name, categoryIds, and serviceIds.

Calendar-first service browsing

Use getFlowAvailability(bookingConfigId, options) to show a calendar first, then show the services available when the visitor clicks a date. Your application renders the calendar and the filtered service cards.

This method is available in DOM Package 1.7.1 and newer and returns Promise<TiquoFlowAvailability>. The options type is GetFlowAvailabilityOptions.

const summary = await tiquo.getFlowAvailability('CUSTOMER_FLOW_ID', {
  dateFrom: '2026-09-14',
  dateTo: '2026-09-20',
  partySize: 2,
});

if (summary.partial) {
  throw new Error('Some availability could not be loaded. Please retry.');
}

const servicesById = new Map(
  summary.services.map((service) => [service.id, service]),
);

// Render summary.days as calendar buttons. Disable unavailable days.
// Call this from a date button's click handler to get its service cards.
function servicesForDate(selectedDate) {
  const day = summary.days.find((day) => day.date === selectedDate);
  return (day?.serviceIds ?? [])
    .map((id) => servicesById.get(id))
    .filter(Boolean);
}

Availability options and response

OptionDescription
dateFromFirst venue-local date, as YYYY-MM-DD
dateToLast venue-local date, inclusive; at most seven dates per request
partySizePeople in one booking; integer from 1 to 1000, default 1. Rooms and tables are offered only when one fits the whole party; classes and events need this many spots free
includeSlotsInclude exact time slots; default false. When true, request only one day
lengthMinutesBooking length to check, in minutes (5 to 1440), for services where the customer chooses the length. Services that don't offer that length are left out; fixed-length services ignore it

Fetch additional calendar pages by advancing the date range one week at a time. Use dates in the venue's timezone, rather than converting them from the visitor's local timezone.

The response includes the same membership-filtered services, categories, catalogSections, and flow information as getFlowServices(), plus:

FieldDescription
dateFrom, dateTo, partySizeRequested range and party size
slotsIncludedWhether exact time slots were requested
daysEvery requested date, including unavailable dates
days[].dateVenue-local date as YYYY-MM-DD
days[].isAvailableWhether any service has bookable capacity that day
days[].serviceIdsIDs of services bookable somewhere on that date
days[].slotsOptional time entries with time and serviceIds for timed services
partialWhether any service availability lookup failed
failedServiceIdsService IDs whose availability could not be loaded

Match each day's serviceIds to the objects in services to render names, images, and prices. Complete results with no availability retain the date with an empty serviceIds array. Daily and nightly services can be available on a date without exposing time slots.

If partial is true, show an incomplete-availability message and retry the request. Do not treat failedServiceIds as sold out. Re-fetch availability when the party size or customer session changes.

Load time slots after choosing a date

// selectedDate is the YYYY-MM-DD value from the clicked calendar day.
const detail = await tiquo.getFlowAvailability('CUSTOMER_FLOW_ID', {
  dateFrom: selectedDate,
  dateTo: selectedDate,
  partySize: 2,
  includeSlots: true,
});

if (detail.partial) {
  throw new Error('Some availability could not be loaded. Please retry.');
}

const servicesById = new Map(
  detail.services.map((service) => [service.id, service]),
);
const slots = (detail.days[0]?.slots ?? []).map((slot) => ({
  time: slot.time,
  services: slot.serviceIds
    .map((id) => servicesById.get(id))
    .filter(Boolean),
}));
// Render slots, then show the services for the customer's chosen time.

After the visitor chooses a service and time, pass the same date, start time, party size and length to embedServiceBooking() so the customer doesn't choose them again. See Carry the customer's choices into the booking. The embedded flow re-checks availability, and the customer can still change anything.

Service fields

Each item in catalog.services uses the TiquoFlowService type:

FieldDescription
idService ID used by getFlowService() and embedServiceBooking()
nameService name
slugOptional service slug
descriptionFull service description
shortDescriptionOptional short description
tagsService tags
imagePreferred display image: the service image, then the first associated category image
featuredImageFeatured service image, with the first service image as fallback
serviceImageFeatured service image, with the first service image as fallback
imagesThe service's image gallery in display order (up to 15). The first entry is the primary image
categoryImagePrimary image of the first flow category containing the service
categoryImagesPrimary image of each flow category containing the service. For a category's full gallery, use catalog.categories[].images
categoryNameName of the first flow category containing the service
categoryNamesNames of all flow categories containing the service
categoryDescriptionFirst configured description from a flow category containing the service
categoryDescriptionsConfigured descriptions from all flow categories containing the service
priceBase service price
effectivePriceCurrent effective price after applicable flow pricing
currencyCurrency code, when configured
priceTypeService price type
serviceTypeService type
durationConfigured service duration
durationTypeDuration unit or booking type
capacityMost guests one booking can hold: the largest room or table that seats one party. Omitted when nothing limits a single booking, such as classes and events that sell spots from a shared pool. Not the partySize you searched with, and not the spots left
locationWhere the service is booked: id, name, sublocationId, sublocationName, and optional address, city, state, postalCode, country, coordinates (lat, lng), and timezone. A sublocation's own address replaces the venue address when one is set
isFeaturedWhether the service is featured
paymentTypeConfigured payment type
depositRequiredWhether the service requires a deposit
depositAmountConfigured deposit amount
depositTypeConfigured deposit type
minAdvanceBookingMinimum advance-booking rule
maxAdvanceBookingMaximum advance-booking rule
checkInTimeCheck-in time, when configured
checkOutTimeCheck-out time, when configured
minStayMinimum stay, when configured
maxStayMaximum stay, when configured
bookingLengthLength choice, when the customer chooses how long to book. See Booking length configuration
categoryIdsIDs of the flow categories containing the service

Use image for a ready-to-display image with automatic category fallback. Use serviceImage or categoryImage when the UI needs to distinguish where the image was configured. images remains service-only, while categoryImages contains the categories' primary image URLs. categoryName is the first associated flow category, while categoryNames contains every associated category name. categoryDescription is the first configured description from an associated category, while categoryDescriptions contains all configured associated category descriptions.

Image galleries

Services and categories each hold an ordered gallery of up to 15 images. Show the whole gallery as a carousel where there is room, such as a detail page or hero card, and only the first image in compact layouts such as thumbnails or list rows. TiquoFlowCategory.images and the service location field are typed in DOM Package 1.11.0 and newer; older API responses may omit a category's images, so fall back to image.

const catalog = await tiquo.getFlowServices('CUSTOMER_FLOW_ID');

for (const category of catalog.categories) {
  const gallery = category.images ?? (category.image ? [category.image] : []);
  renderCategoryHero(category.name, gallery);
}

for (const service of catalog.services) {
  renderServiceCard(service.name, service.images[0] ?? service.image);
  console.log(service.location?.name, service.location?.postalCode);
}

Load one service

Use getFlowService() on a detail page:

const service = await tiquo.getFlowService(
  'CUSTOMER_FLOW_ID',
  'SERVICE_ID'
);

if (!service) {
  // The service does not exist in this flow or is unavailable to this visitor.
}

The method returns null when the service is not in the flow catalog visible to the current visitor. It does not fall back to a different service.

Embed booking for the selected service

After rendering your custom service content, call embedServiceBooking():

<div id="service-booking"></div>
await tiquo.embedServiceBooking(
  'CUSTOMER_FLOW_ID',
  service.id,
  '#service-booking',
  {
    width: '100%',
    minHeight: '240px',
    autoResize: true,
  }
);

The embed:

  • Preselects the exact service before the flow renders
  • Removes the category and service-catalog navigation
  • Prevents the customer from returning to select another service
  • Keeps the flow's normal date, quantity, availability, payment, and booking steps
  • Reuses the DOM Package's secure authenticated iframe handoff
  • Automatically resizes unless autoResize is set to false

If the service ID is missing, invalid, or unavailable to the current customer, the embedded flow does not fall back to the full service catalog.

Carry the customer's choices into the booking

If your page already asked for the day, time, number of people or booking length (for example with getFlowAvailability()), pass them on so the customer doesn't choose them again:

await tiquo.embedServiceBooking('CUSTOMER_FLOW_ID', service.id, '#service-booking', {
  date: '2026-10-14',   // a days[].date, venue-local YYYY-MM-DD
  startTime: '14:00',   // a days[].slots[].time on that date, HH:mm
  partySize: 6,         // the partySize you searched with
  lengthMinutes: 120,   // for a service where the customer chooses the length
});
OptionDescription
dateVenue-local date, YYYY-MM-DD. The calendar opens on it and selects it while it's bookable
startTimeStart time on date, HH:mm. Selected only while it's still free for the booking length and party size; otherwise the customer sees that day's remaining times. Requires date
partySizePeople in the booking, 1 to 1000. Sets the flow's people stepper (for example "Number of Attendees"), kept within the flow's minimum and maximum. Flows that sell a quantity of tickets instead ignore it
lengthMinutesBooking length in minutes, for a bookingLength range
lengthOptionIdOne of the service's bookingLength.options[].id

A pre-selected time behaves like the customer's own click: flows set to move on automatically continue to the details step. Everything stays editable, and availability is checked again when the customer books.

date, startTime and partySize need DOM Package 1.11.0 or newer. Values in the wrong format throw a TiquoAuthError with code INVALID_SERVICE_BOOKING_START (date or time) or INVALID_PARTY_SIZE.

Embed booking for a selected category

Use embedCustomerFlow() with categoryId and lockCategory=true to embed a category-locked booking flow. This is supported on the /embed/ surface used by both the DOM Package and Hosted Package.

For example, a Boxing Sessions category can contain separate Member, Guest, and Public services. A category card should open that category and let the customer choose their eligible booking type in the flow's own service step. The category is preselected and cannot be changed inside the embed; the remaining steps follow the flow's configured order.

Use getFlowServices() to load the catalog and render one card per entry in catalog.categories. Store each category's id on its card and pass the clicked category's ID when opening the booking. Do not choose the first service in the category or group categories by name alone.

<div id="category-booking"></div>
const bookingConfigId = 'CUSTOMER_FLOW_ID';
const catalog = await tiquo.getFlowServices(bookingConfigId);
const categoryId = 'CATEGORY_ID'; // ID from the clicked category card.
const category = catalog.categories.find((item) => item.id === categoryId);

if (!category) throw new Error('Category is unavailable in this flow');

const url = new URL(
  `https://book.tiquo.app/embed/${encodeURIComponent(bookingConfigId)}`
);
url.searchParams.set('categoryId', category.id);
url.searchParams.set('lockCategory', 'true');

await tiquo.embedCustomerFlow(url.toString(), '#category-booking', {
  minHeight: '240px',
  autoResize: true,
});

Replace CATEGORY_ID with the clicked card's catalog.categories[].id. CUSTOMER_FLOW_ID is the booking configuration ID already used with getFlowServices() and found in the flow's booking URL.

Leave serviceId and lockService out of category URLs. A locked service takes precedence and prevents the customer from choosing another booking type. Continue using embedServiceBooking() when the customer has already chosen one specific service.

Customer authentication and booking-type visibility

The embed uses the package's authenticated customer session and the flow's normal membership and service-access rules. Ensure the customer is signed in through the package before opening the embed when member options are needed. Signing into the separate member portal alone does not establish that website session. embedCustomerFlow() handles the authenticated iframe handoff and automatic resizing.

Category locking narrows the selection; it does not grant access to Member, Guest, or Public services. The options offered depend on the customer's access and the flow configuration. An invalid category or one with no accessible services does not fall back to unrelated services.

Is the additional flowId parameter required?

No. A correctly configured public embed resolves the flow using the booking configuration ID in /embed/CUSTOMER_FLOW_ID.

The portal may also include flowId, the underlying flow record ID returned as catalog.flow.id. It is distinct from catalog.bookingConfigId and is used for a fallback lookup where permitted. It does not authenticate the customer or enable member booking types. Omit it from the category embed shown above.

Custom catalog and detail page pattern

A typical integration uses two application routes:

  1. The catalog route calls getFlowServices() and renders a card or block for each service.
  2. Each card links to a custom detail route with the service ID in the route or query string.
  3. The detail route calls getFlowService() and renders the service's image, description, price, and other fields.
  4. The detail route calls embedServiceBooking() inside the booking section.

This keeps discovery and page design fully controlled by your application while Tiquo handles the final availability and booking journey.

Booking length configuration

A returned service can include bookingLength for calendar-based Minutes or Hourly bookings. With mode: 'options', each option has an id, minutes, optional label, price, and optional isDefault. With mode: 'range', the configuration supplies minMinutes, maxMinutes, stepMinutes, defaultMinutes, and optional maxPrice.

Use this configuration to explain the available lengths in a custom catalogue. The Tiquo booking flow checks the selected length against availability and calculates the final price. A catalogue response does not reserve a slot or establish that the full duration is available.

Sur cette page