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.
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-jsorwiil)
Quick start
- TypeScript
- Python
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}`);
import os
from time import time
from wiil import WiilClient
from wiil.models.business_mgt import CreateBusinessService, CreateServiceAppointment
client = WiilClient(api_key=os.environ["WIIL_API_KEY"])
# 1. Create a service
service = client.business_services.create(
CreateBusinessService(
name="Professional Haircut",
description="Premium haircut service with styling",
duration=45,
buffer_time=15,
price=50.00,
is_bookable=True,
is_active=True,
)
)
# 2. Book an appointment
start_time = int(time() * 1000) + 24 * 60 * 60 * 1000
appointment = client.service_appointments.create(
CreateServiceAppointment(
business_service_id=service.id,
customer_id="cust_123",
start_time=start_time,
end_time=start_time + (45 * 60 * 1000),
duration=45,
total_price=50.00,
deposit_paid=0,
)
)
# 3. Confirm it
confirmed = client.service_appointments.update_status(appointment.id, "confirmed")
print(service.id, appointment.id, confirmed.status)
Business services
A business service defines an offering with its pricing, duration, and booking rules.
Create a service
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import CreateBusinessService
service = client.business_services.create(
CreateBusinessService(
name="Massage Therapy",
description="60-minute therapeutic massage",
duration=60,
buffer_time=10,
price=80.00,
display_order=2,
)
)
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
- TypeScript
- Python
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');
from wiil.models.business_mgt import UpdateBusinessService
from wiil.types import PaginationRequest
loaded = client.business_services.get("service_123")
services = client.business_services.list(PaginationRequest(page=1, page_size=20))
updated = client.business_services.update(
UpdateBusinessService(id="service_123", name="Premium Massage Therapy", price=90.00)
)
deleted = client.business_services.delete(updated.id)
print(loaded.name, services.meta.total_count, deleted)
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.
- TypeScript
- Python
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`);
from wiil.models.business_mgt import CreateBusinessService
services = client.business_services.create_batch([
CreateBusinessService(name="30-Minute Massage", description="Relaxation massage", duration=30, price=50.00, is_bookable=True),
CreateBusinessService(name="60-Minute Massage", description="Deep tissue massage", duration=60, price=80.00, is_bookable=True),
CreateBusinessService(name="90-Minute Massage", description="Full body therapeutic massage", duration=90, price=110.00, is_bookable=True),
])
print(f"Created {len(services.data)} 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
- TypeScript
- Python
// 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,
});
from time import time
from wiil.models.business_mgt import CreateServiceAppointment
start_ms = int(time() * 1000) + 3600000
appointment = client.service_appointments.create(
CreateServiceAppointment(
business_service_id="service_123",
customer_id="cust_456",
start_time=start_ms,
end_time=start_ms + 60 * 60 * 1000,
duration=60,
total_price=80.00,
deposit_paid=20.00,
)
)
Get, by customer, by service
- TypeScript
- Python
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();
from wiil.types import PaginationRequest
loaded = client.service_appointments.get("appointment_123")
customer_appointments = client.service_appointments.get_by_customer(
"cust_456", PaginationRequest(page=1, page_size=20)
)
service_appointments = client.service_appointments.get_by_service(
"service_123", PaginationRequest(page=1, page_size=20)
)
Appointment lifecycle
Appointments follow the lifecycle pending → confirmed → completed, with cancelled and no_show as terminal states.
| Status | Meaning |
|---|---|
pending | Awaiting confirmation |
confirmed | Confirmed by the business |
completed | Service completed |
cancelled | Cancelled by customer or business |
no_show | Customer did not arrive |
Update status
- TypeScript
- Python
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);
confirmed = client.service_appointments.update_status("appointment_123", "confirmed")
completed = client.service_appointments.update_status("appointment_123", "completed")
no_show = client.service_appointments.update_status("appointment_123", "no_show")
Cancel
- TypeScript
- Python
const cancelled = await client.serviceAppointments.cancel('appointment_123', {
cancelReason: 'Customer requested cancellation',
});
console.log(`${cancelled.status} — ${cancelled.cancelReason}`);
cancelled = client.service_appointments.cancel(
"appointment_123", reason="Customer requested cancellation"
)
print(cancelled.status)
Reschedule
- TypeScript
- Python
// 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',
});
from time import time
rescheduled = client.service_appointments.reschedule(
"appointment_123",
start_time=str(int(time() * 1000) + 48 * 60 * 60 * 1000),
end_time=str(int(time() * 1000) + 49 * 60 * 60 * 1000),
)
print(rescheduled.start_time)
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
getAvailableSlotsand pass the slot'sstartTimeUtcSec/endTimeUtcSecdirectly (the API expects UTC seconds). - Follow the status progression —
pending → confirmed → completed; record acancelReasonwhen cancelling. - Use buffers —
bufferBefore/bufferAfter(TypeScript) prevent back-to-back scheduling conflicts. - Handle errors — check for
404(service or customer not found) and409(slot already booked) on create.
Next steps
- Catalog reference: Services & Categories, Service providers, Appointments
- AI-powered booking: integrate with WIIL agents for conversational booking
- Notifications: set up appointment reminders via outbound communications