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 basePricebase_price, durationSegmentsduration_segments).

BusinessServiceConfig

FieldTypeRequiredDefaultDescription
idstringautoUnique identifier (read-only)
namestringYesService name
descriptionstring | nullNoDetailed description
imageUrlstring | nullNoService image URL
categoryIdstring | nullNoService category ID
bookingCodestring | nullNoShort booking code
durationnumberYes60Duration in minutes (max 480)
durationSegmentsobject | nullNoSegmented duration breakdown
bufferBeforenumberYes0Buffer before appointment (minutes)
bufferAfternumberYes0Buffer after appointment (minutes)
isBookablebooleanYestrueCan be booked online
maxConcurrentBookingsnumber | nullNoMax simultaneous bookings (null = 1)
serviceAvailabilityobject | nullNoService-specific availability
basePricenumberYes0Base price in account currency
priceModeenum | nullNoFIXED, STARTS_AT, or VARIABLE
gratuityModeenum | nullNoNONE, OPTIONAL, or REQUIRED
isActivebooleanYestrueCurrently available
displayOrdernumber | nullNoDisplay order in listings
channelMappingsarray | nullNoPer-channel service ID mappings
requiredResourcesstring[]Yes[]Required resource IDs
bookingRulesobject | nullNoBooking constraints
depositStrategyenum | nullNoNONE, FIXED, or PERCENTAGE
depositValuenumber | nullNoDeposit amount or percentage
lateCancelFeePercentnumberYes0Late cancellation fee %
noShowFeePercentnumberYes0No-show fee %
requiredDatafieldConfigobject | nullNoService-level appointment-field overrides (see below)
createdAt / updatedAtnumberautoTimestamps (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
idstringautoUnique identifier (read-only)
namestringYesCategory name
descriptionstring | nullNoCategory description
imageUrlstring | nullNoCategory image URL
channelMappingsarray | nullNoPer-channel category ID mappings
displayOrdernumber | nullNoDisplay order in listing
isActivebooleanYestrueWhether category is active
createdAt / updatedAtnumberautoTimestamps (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
weeklyScheduleobjectWeekly schedule (required when SCHEDULED)
dateRangesarraySeasonal 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.