Skip to main content

Services & Categories

A business service defines a single offering with its pricing, scheduling, and booking rules. Service categories group related services for organization and display.

SDK parity

The TypeScript and Python SDKs expose the same resources, methods, and fields. Every example below is shown in both languages. The only difference is naming convention: TypeScript uses camelCase (basePrice), Python uses the snake_case equivalent (base_price).

Business services​

Create a service​

import { WiilClient } from 'wiil-js';

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

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,
});

console.log(`Service Created: ${service.name} ($${service.basePrice})`);

Get and list​

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

const result = await client.businessServices.list({ page: 1, pageSize: 20 });
console.log(`Total Services: ${result.meta.totalCount}`);
result.data.forEach((svc) => {
console.log(`- ${svc.name}: $${svc.basePrice} (${svc.duration} min)`);
});

Update and delete​

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

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

Batch create​

Create multiple services in a single request.

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`);
Limits

Both SDKs cap batch creation at 50 services per request. Each item is validated independently, and validation errors report the offending index.

A fully configured service​

Services support far more than name and price. This example sets duration segments, buffers, a pricing mode, gratuity policy, booking rules, and a deposit — matching the canonical BusinessServiceConfig schema.

const service = await client.businessServices.create({
organizationId: 'org_123',
name: "Women's Haircut",
description: 'Professional haircut with styling',
categoryId: 'cat_hair',
duration: 60,
durationSegments: {
prep: 5,
active: 45,
processing: 0,
finish: 10,
turnover: 5,
},
bufferBefore: 0,
bufferAfter: 15,
isBookable: true,
maxConcurrentBookings: 1,
basePrice: 75.00,
priceMode: 'FIXED',
gratuityMode: 'OPTIONAL',
isActive: true,
displayOrder: 1,
requiredResources: [],
bookingRules: {
onlineEnabled: true,
existingOnly: false,
requiresConsult: false,
maxDaysOut: 30,
minNoticeHours: 2,
lateCancelHours: 24,
},
depositStrategy: 'PERCENTAGE',
depositValue: 25,
lateCancelFeePercent: 50,
noShowFeePercent: 100,
});

Service categories​

Categories group related services for navigation and display. Assign a service to a category with the service's categoryId (category_id in Python).

Create a category​

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

console.log(`Category Created: ${category.name}`);

Get, update, list, and delete​

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

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

const result = await client.serviceCategories.list();
result.data.forEach((cat) => {
console.log(`- ${cat.name} (order: ${cat.displayOrder})`);
});

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

Batch create categories​

const result = await client.serviceCategories.createBatch([
{
organizationId: 'org_123',
name: 'Spa Treatments',
description: 'Relaxing spa services',
displayOrder: 10,
isActive: true,
},
{
organizationId: 'org_123',
name: 'Consultations',
description: 'Professional consultations',
displayOrder: 11,
isActive: true,
},
]);

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

Field reference​

Field names below use the TypeScript (camelCase) form. The Python SDK accepts the snake_case equivalent of each name (for example basePrice → base_price, durationSegments → duration_segments).

BusinessServiceConfig​

FieldTypeRequiredDefaultDescription
idstringauto—Unique identifier (read-only)
namestringYes—Service name
descriptionstring | nullNo—Detailed description
imageUrlstring | nullNo—Service image URL
categoryIdstring | nullNo—Service category ID
bookingCodestring | nullNo—Short booking code
durationnumberYes60Duration in minutes (max 480)
durationSegmentsobject | nullNo—Segmented duration breakdown
bufferBeforenumberYes0Buffer before appointment (minutes)
bufferAfternumberYes0Buffer after appointment (minutes)
isBookablebooleanYestrueCan be booked online
maxConcurrentBookingsnumber | nullNo—Max simultaneous bookings (null = 1)
serviceAvailabilityobject | nullNo—Service-specific availability
basePricenumberYes0Base price in account currency
priceModeenum | nullNo—FIXED, STARTS_AT, or VARIABLE
gratuityModeenum | nullNo—NONE, OPTIONAL, or REQUIRED
isActivebooleanYestrueCurrently available
displayOrdernumber | nullNo—Display order in listings
channelMappingsarray | nullNo—Per-channel service ID mappings
requiredResourcesstring[]Yes[]Required resource IDs
bookingRulesobject | nullNo—Booking constraints
depositStrategyenum | nullNo—NONE, FIXED, or PERCENTAGE
depositValuenumber | nullNo—Deposit amount or percentage
lateCancelFeePercentnumberYes0Late cancellation fee %
noShowFeePercentnumberYes0No-show fee %
requiredDatafieldConfigobject | nullNo—Service-level appointment-field overrides (see below)
createdAt / updatedAtnumberauto—Timestamps (read-only)
maxConcurrentBookings

Leave maxConcurrentBookings unset (null) for one-at-a-time services where a single customer occupies the slot — the default behaves as 1. Set it higher for services that can host several customers in the same window, such as group classes, workshops, or a station with multiple chairs (for example maxConcurrentBookings: 8 allows up to eight simultaneous bookings of the same slot).

requiredDatafieldConfig (service-level appointment fields)​

requiredDatafieldConfig embeds a ServiceAppointmentFieldConfig on the service. It tailors the organization's appointment-field library for this specific service — inheriting a subset of fields, overriding individual properties, and adding service-only fields that the base appointment model doesn't capture.

FieldTypeDefaultDescription
inheritedFieldKeysstring[][]fieldKey values pulled in from the org-level library
fieldOverridesarray[]Per-field overrides (relabel, make required, etc.)
additionalFieldsarray[]Fields unique to this service
isActivebooleantrueWhether this service config is active
reuseDetailsbooleanfalseReuse the customer's previous values for this service

See Appointment Fields for the full hierarchy, field definitions, validation, conditional logic, and how AI agents capture these values at booking.

ServiceCategory​

FieldTypeRequiredDefaultDescription
idstringauto—Unique identifier (read-only)
namestringYes—Category name
descriptionstring | nullNo—Category description
imageUrlstring | nullNo—Category image URL
channelMappingsarray | nullNo—Per-channel category ID mappings
displayOrdernumber | nullNo—Display order in listing
isActivebooleanYestrueWhether category is active
createdAt / updatedAtnumberauto—Timestamps (read-only)

ServiceDurationSegments​

Breaks down service time into distinct phases. The sum should match duration.

FieldTypeDefaultDescription
prepnumber0Preparation time in minutes
activenumber60Hands-on active service time
processingnumber0Processing / wait time
finishnumber0Finishing time
turnovernumber0Turnover / reset time

ServiceBookingRules​

FieldTypeDefaultDescription
onlineEnabledbooleantrueOnline booking enabled
existingOnlybooleanfalseOnly existing customers can book
requiresConsultbooleanfalseConsultation required first
maxDaysOutnumber30Max days in advance
minNoticeHoursnumber0Minimum booking notice (hours)
lateCancelHoursnumber24Late cancellation threshold (hours)

ServiceAvailability​

FieldTypeDefaultDescription
modeenumINHERITALWAYS, SCHEDULED, or INHERIT
weeklyScheduleobject—Weekly schedule (required when SCHEDULED)
dateRangesarray—Seasonal availability or blackout periods

Enums​

EnumValues
ServicePriceModeFIXED, STARTS_AT, VARIABLE
ServiceGratuityModeNONE, OPTIONAL, REQUIRED
ServiceAvailabilityModeALWAYS, SCHEDULED, INHERIT
ServiceDepositStrategyNONE, FIXED, PERCENTAGE
tip

Organize related services into categories for cleaner navigation, and use duration segments and buffers to model real chair/room time and prevent scheduling conflicts.