Skip to main content

Business Services & Appointments Guide

Manage services and schedule appointments for your service-based business.

This guide is ideal for salons, spas, medical clinics, consulting firms, and any business offering time-based services. It covers business services, service categories, providers and staff, slot availability, and the appointment lifecycle.

SDK conventions

TypeScript examples follow the wiil-js SDK; Python examples follow the wiil SDK. Field names differ by language convention — TypeScript uses camelCase (basePrice), Python uses snake_case (base_price/price). Where the two SDKs expose the same operation, both are shown in tabs; TypeScript-only sections are labeled.

Prerequisites

  • Active WIIL Platform account
  • API key with business-management permissions
  • The SDK installed for your language (wiil-js or wiil)

Quick start

import { WiilClient } from 'wiil-js';
import { AppointmentStatus } from 'wiil-core-js';

const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });

// 1. Create a service
const service = await client.businessServices.create({
organizationId: 'org_123',
name: 'Professional Haircut',
description: 'Premium haircut service with styling',
duration: 45,
bufferBefore: 0,
bufferAfter: 15,
basePrice: 50.00,
isBookable: true,
isActive: true,
requiredResources: [],
lateCancelFeePercent: 50,
noShowFeePercent: 100,
});

// 2. Create a provider
const provider = await client.servicePersons.create({
name: 'Jane Smith',
description: 'Senior Hair Stylist',
isActive: true,
bookableOnline: true,
bookableByStaff: true,
});

// 3. Query available slots
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const slotResponse = await client.serviceAppointments.getAvailableSlots({
serviceId: service.id,
localDate: tomorrow.toISOString().split('T')[0],
providerId: provider.id,
maxResults: 20,
});

// 4. Book the first slot (API expects UTC seconds)
const slot = slotResponse.slots[0];
const appointment = await client.serviceAppointments.create({
businessServiceId: service.id,
customerId: 'cust_123',
startTime: slot.startTimeUtcSec,
endTime: slot.endTimeUtcSec,
duration: Math.round((slot.endTimeUtcSec - slot.startTimeUtcSec) / 60),
totalPrice: 50.00,
depositPaid: 0,
});

// 5. Confirm it
const confirmed = await client.serviceAppointments.updateStatus(
appointment.id,
AppointmentStatus.CONFIRMED,
);
console.log(`Appointment ${confirmed.id}: ${confirmed.status}`);

Business services

A business service defines an offering with its pricing, duration, and booking rules.

Create a service

const service = await client.businessServices.create({
organizationId: 'org_123',
name: 'Massage Therapy',
description: '60-minute therapeutic massage',
duration: 60,
bufferBefore: 10,
bufferAfter: 5,
basePrice: 80.00,
isBookable: true,
isActive: true,
requiredResources: [],
lateCancelFeePercent: 50,
noShowFeePercent: 100,
});
Field reference

For the complete BusinessServiceConfig schema — duration segments, booking rules, pricing modes, deposits, and per-service appointment fields — see the Services & Categories catalog page.

Get, update, list, delete

const service = await client.businessServices.get('service_123');

const updated = await client.businessServices.update({
id: 'service_123',
name: 'Premium Massage Therapy',
basePrice: 90.00,
});

const result = await client.businessServices.list({ page: 1, pageSize: 20 });
console.log(`Total Services: ${result.meta.totalCount}`);

await client.businessServices.delete('service_123');

Batch create

Create multiple services in a single request — up to 50 per batch. Each item is validated independently; if one fails, the batch fails with the offending index.

const result = await client.businessServices.createBatch([
{
organizationId: 'org_123',
name: 'Quick Consultation',
description: '30-minute consultation',
duration: 30,
bufferBefore: 5,
bufferAfter: 5,
basePrice: 49.99,
isBookable: true,
isActive: true,
requiredResources: [],
lateCancelFeePercent: 50,
noShowFeePercent: 100,
},
{
organizationId: 'org_123',
name: 'Standard Session',
description: '60-minute session',
duration: 60,
bufferBefore: 10,
bufferAfter: 5,
basePrice: 89.99,
isBookable: true,
isActive: true,
requiredResources: [],
lateCancelFeePercent: 50,
noShowFeePercent: 100,
},
]);

console.log(`Created ${result.data.length} services`);

Service categories

Organize services into categories for navigation and display. (TypeScript SDK)

const category = await client.serviceCategories.create({
organizationId: 'org_123',
name: 'Hair Services',
description: 'All hair-related services',
displayOrder: 1,
isActive: true,
});

const loaded = await client.serviceCategories.get('category_123');
const all = await client.serviceCategories.list();

await client.serviceCategories.update({
id: 'category_123',
name: 'Premium Hair Services',
displayOrder: 2,
});

await client.serviceCategories.delete('category_123');

Service persons & providers

A service person is a staff member; a service provider links a service to a person, with optional price and duration overrides. (TypeScript SDK)

// Create a staff member
const person = await client.servicePersons.create({
name: 'Jane Smith',
description: 'Senior hair stylist with 10 years experience',
locationId: 'loc_123',
commissionPercent: 35,
bookableOnline: true,
bookableByStaff: true,
isActive: true,
});

const staff = await client.servicePersons.getByLocation('loc_123');

// Link a service to a provider
const link = await client.serviceProviders.create({
serviceId: 'service_123',
providerId: person.id,
priceOverride: 75.00,
durationOverride: 45,
active: true,
});

const providersForService = await client.serviceProviders.getByService('service_123');
const servicesForProvider = await client.serviceProviders.getByProvider(person.id);

Service appointments

Query available slots

Always query availability before booking. Slots return both a display time and UTC seconds. (TypeScript SDK)

const slotResponse = await client.serviceAppointments.getAvailableSlots({
serviceId: 'service_123',
localDate: '2026-06-22',
providerId: 'person_123',
maxResults: 20,
});

slotResponse.slots.forEach((slot, idx) => {
console.log(`${idx + 1}. ${slot.startTimeOfDay} — startUtc: ${slot.startTimeUtcSec}`);
});

Create an appointment

// API expects UTC seconds — use slot times directly
const slot = slotResponse.slots[0];

const appointment = await client.serviceAppointments.create({
businessServiceId: 'service_123',
customerId: 'cust_456',
startTime: slot.startTimeUtcSec,
endTime: slot.endTimeUtcSec,
duration: Math.round((slot.endTimeUtcSec - slot.startTimeUtcSec) / 60),
totalPrice: 80.00,
depositPaid: 20.00,
});

Get, by customer, by service

const appointment = await client.serviceAppointments.get('appointment_123');

const byCustomer = await client.serviceAppointments.getByCustomer('cust_123', { page: 1, pageSize: 20 });
const byService = await client.serviceAppointments.getByService('service_123', { page: 1, pageSize: 20 });
const all = await client.serviceAppointments.list();

Appointment lifecycle

Appointments follow the lifecycle pending → confirmed → completed, with cancelled and no_show as terminal states.

StatusMeaning
pendingAwaiting confirmation
confirmedConfirmed by the business
completedService completed
cancelledCancelled by customer or business
no_showCustomer did not arrive

Update status

import { AppointmentStatus } from 'wiil-core-js';

const confirmed = await client.serviceAppointments.updateStatus('appointment_123', AppointmentStatus.CONFIRMED);
const completed = await client.serviceAppointments.updateStatus('appointment_123', AppointmentStatus.COMPLETED);
const noShow = await client.serviceAppointments.updateStatus('appointment_123', AppointmentStatus.NO_SHOW);

Cancel

const cancelled = await client.serviceAppointments.cancel('appointment_123', {
cancelReason: 'Customer requested cancellation',
});

console.log(`${cancelled.status}${cancelled.cancelReason}`);

Reschedule

// API expects UTC seconds
const newStartTime = Math.floor(Date.now() / 1000) + 48 * 60 * 60; // 2 days from now
const newEndTime = newStartTime + 60 * 60;

const rescheduled = await client.serviceAppointments.reschedule('appointment_123', {
startTime: newStartTime,
endTime: newEndTime,
});

// Reschedule onto a different service
const moved = await client.serviceAppointments.reschedule('appointment_123', {
startTime: newStartTime,
endTime: newEndTime,
businessServiceId: 'service_456',
});

Complete example: salon booking

import { WiilClient } from 'wiil-js';
import { AppointmentStatus, PreferredContactMethod } from 'wiil-core-js';

async function setupSalonBooking() {
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });

// 1. Create a customer
const customer = await client.customers.create({
phone_number: '+15551234567',
firstname: 'Jane',
lastname: 'Doe',
preferred_language: 'en',
preferred_contact_method: PreferredContactMethod.EMAIL,
isValidatedNames: false,
});

// 2. Create a category and a service
const category = await client.serviceCategories.create({
organizationId: 'org_salon',
name: 'Hair Services',
description: 'All hair-related services',
displayOrder: 1,
isActive: true,
});

const haircut = await client.businessServices.create({
organizationId: 'org_salon',
name: 'Haircut & Style',
description: 'Professional haircut with styling',
duration: 60,
bufferBefore: 0,
bufferAfter: 0,
basePrice: 50.00,
isBookable: true,
isActive: true,
requiredResources: [],
lateCancelFeePercent: 50,
noShowFeePercent: 100,
});

// 3. Create a stylist and link the service
const stylist = await client.servicePersons.create({
name: 'Sarah Johnson',
description: 'Senior stylist',
isActive: true,
bookableOnline: true,
bookableByStaff: true,
});

await client.serviceProviders.create({
serviceId: haircut.id,
providerId: stylist.id,
active: true,
});

// 4. Query slots and book the first one
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const slotResponse = await client.serviceAppointments.getAvailableSlots({
serviceId: haircut.id,
localDate: tomorrow.toISOString().split('T')[0],
providerId: stylist.id,
maxResults: 10,
});

if (slotResponse.slots.length === 0) return;

const slot = slotResponse.slots[0];
const appointment = await client.serviceAppointments.create({
businessServiceId: haircut.id,
customerId: customer.id,
startTime: slot.startTimeUtcSec,
endTime: slot.endTimeUtcSec,
duration: Math.round((slot.endTimeUtcSec - slot.startTimeUtcSec) / 60),
totalPrice: 50.00,
depositPaid: 0,
});

// 5. Confirm
const confirmed = await client.serviceAppointments.updateStatus(appointment.id, AppointmentStatus.CONFIRMED);

return { customer, category, service: haircut, stylist, appointment: confirmed };
}

setupSalonBooking().catch(console.error);

Best practices

  • Always query slots before booking — use getAvailableSlots and pass the slot's startTimeUtcSec/endTimeUtcSec directly (the API expects UTC seconds).
  • Follow the status progressionpending → confirmed → completed; record a cancelReason when cancelling.
  • Use buffersbufferBefore/bufferAfter (TypeScript) prevent back-to-back scheduling conflicts.
  • Handle errors — check for 404 (service or customer not found) and 409 (slot already booked) on create.

Next steps


← Back to Guides