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.
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
- TypeScript
- Python
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})`);
import os
from wiil import WiilClient
from wiil.models.business_mgt import CreateBusinessService
client = WiilClient(api_key=os.environ["WIIL_API_KEY"])
service = client.business_services.create(
CreateBusinessService(
organization_id="org_123",
name="Professional Haircut",
description="Premium haircut service with styling",
duration=45,
buffer_before=0,
buffer_after=15,
base_price=50.00,
is_bookable=True,
is_active=True,
required_resources=[],
late_cancel_fee_percent=50,
no_show_fee_percent=100,
)
)
print(f"Service Created: {service.name} (${service.base_price})")
Get and list
- TypeScript
- Python
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)`);
});
from wiil.types import PaginationRequest
loaded = client.business_services.get("service_123")
result = client.business_services.list(PaginationRequest(page=1, page_size=20))
print(f"Total Services: {result.meta.total_count}")
for svc in result.data:
print(f"- {svc.name}: ${svc.base_price} ({svc.duration} min)")
Update and delete
- TypeScript
- Python
const updated = await client.businessServices.update({
id: 'service_123',
name: 'Premium Massage Therapy',
basePrice: 90.00,
});
await client.businessServices.delete('service_123');
from wiil.models.business_mgt import UpdateBusinessService
updated = client.business_services.update(
UpdateBusinessService(
id="service_123",
name="Premium Massage Therapy",
base_price=90.00,
)
)
client.business_services.delete("service_123")
Batch create
Create multiple services in a single request.
- 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
result = client.business_services.create_batch([
CreateBusinessService(
organization_id="org_123",
name="Quick Consultation",
description="30-minute consultation",
duration=30,
buffer_before=5,
buffer_after=5,
base_price=49.99,
is_bookable=True,
is_active=True,
required_resources=[],
late_cancel_fee_percent=50,
no_show_fee_percent=100,
),
CreateBusinessService(
organization_id="org_123",
name="Standard Session",
description="60-minute session",
duration=60,
buffer_before=10,
buffer_after=5,
base_price=89.99,
is_bookable=True,
is_active=True,
required_resources=[],
late_cancel_fee_percent=50,
no_show_fee_percent=100,
),
])
print(f"Created {len(result.data)} services")
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.
- TypeScript
- Python
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,
});
from wiil.models.business_mgt import (
CreateBusinessService,
ServiceDurationSegments,
ServiceBookingRules,
)
service = client.business_services.create(
CreateBusinessService(
organization_id="org_123",
name="Women's Haircut",
description="Professional haircut with styling",
category_id="cat_hair",
duration=60,
duration_segments=ServiceDurationSegments(
prep=5,
active=45,
processing=0,
finish=10,
turnover=5,
),
buffer_before=0,
buffer_after=15,
is_bookable=True,
max_concurrent_bookings=1,
base_price=75.00,
price_mode="FIXED",
gratuity_mode="OPTIONAL",
is_active=True,
display_order=1,
required_resources=[],
booking_rules=ServiceBookingRules(
online_enabled=True,
existing_only=False,
requires_consult=False,
max_days_out=30,
min_notice_hours=2,
late_cancel_hours=24,
),
deposit_strategy="PERCENTAGE",
deposit_value=25,
late_cancel_fee_percent=50,
no_show_fee_percent=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
- TypeScript
- Python
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}`);
from wiil.models.business_mgt import CreateServiceCategory
category = client.service_categories.create(
CreateServiceCategory(
organization_id="org_123",
name="Hair Services",
description="All hair-related services",
display_order=1,
is_active=True,
)
)
print(f"Category Created: {category.name}")
Get, update, list, and delete
- TypeScript
- Python
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');
from wiil.models.business_mgt import UpdateServiceCategory
loaded = client.service_categories.get("category_123")
updated = client.service_categories.update(
UpdateServiceCategory(
id="category_123",
name="Premium Hair Services",
display_order=2,
)
)
result = client.service_categories.list()
for cat in result.data:
print(f"- {cat.name} (order: {cat.display_order})")
client.service_categories.delete("category_123")
Batch create categories
- TypeScript
- Python
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`);
from wiil.models.business_mgt import CreateServiceCategory
result = client.service_categories.create_batch([
CreateServiceCategory(
organization_id="org_123",
name="Spa Treatments",
description="Relaxing spa services",
display_order=10,
is_active=True,
),
CreateServiceCategory(
organization_id="org_123",
name="Consultations",
description="Professional consultations",
display_order=11,
is_active=True,
),
])
print(f"Created {len(result.data)} 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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | auto | — | Unique identifier (read-only) |
name | string | Yes | — | Service name |
description | string | null | No | — | Detailed description |
imageUrl | string | null | No | — | Service image URL |
categoryId | string | null | No | — | Service category ID |
bookingCode | string | null | No | — | Short booking code |
duration | number | Yes | 60 | Duration in minutes (max 480) |
durationSegments | object | null | No | — | Segmented duration breakdown |
bufferBefore | number | Yes | 0 | Buffer before appointment (minutes) |
bufferAfter | number | Yes | 0 | Buffer after appointment (minutes) |
isBookable | boolean | Yes | true | Can be booked online |
maxConcurrentBookings | number | null | No | — | Max simultaneous bookings (null = 1) |
serviceAvailability | object | null | No | — | Service-specific availability |
basePrice | number | Yes | 0 | Base price in account currency |
priceMode | enum | null | No | — | FIXED, STARTS_AT, or VARIABLE |
gratuityMode | enum | null | No | — | NONE, OPTIONAL, or REQUIRED |
isActive | boolean | Yes | true | Currently available |
displayOrder | number | null | No | — | Display order in listings |
channelMappings | array | null | No | — | Per-channel service ID mappings |
requiredResources | string[] | Yes | [] | Required resource IDs |
bookingRules | object | null | No | — | Booking constraints |
depositStrategy | enum | null | No | — | NONE, FIXED, or PERCENTAGE |
depositValue | number | null | No | — | Deposit amount or percentage |
lateCancelFeePercent | number | Yes | 0 | Late cancellation fee % |
noShowFeePercent | number | Yes | 0 | No-show fee % |
requiredDatafieldConfig | object | null | No | — | Service-level appointment-field overrides (see below) |
createdAt / updatedAt | number | auto | — | Timestamps (read-only) |
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.
| Field | Type | Default | Description |
|---|---|---|---|
inheritedFieldKeys | string[] | [] | fieldKey values pulled in from the org-level library |
fieldOverrides | array | [] | Per-field overrides (relabel, make required, etc.) |
additionalFields | array | [] | Fields unique to this service |
isActive | boolean | true | Whether this service config is active |
reuseDetails | boolean | false | Reuse 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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | auto | — | Unique identifier (read-only) |
name | string | Yes | — | Category name |
description | string | null | No | — | Category description |
imageUrl | string | null | No | — | Category image URL |
channelMappings | array | null | No | — | Per-channel category ID mappings |
displayOrder | number | null | No | — | Display order in listing |
isActive | boolean | Yes | true | Whether category is active |
createdAt / updatedAt | number | auto | — | Timestamps (read-only) |
ServiceDurationSegments
Breaks down service time into distinct phases. The sum should match duration.
| Field | Type | Default | Description |
|---|---|---|---|
prep | number | 0 | Preparation time in minutes |
active | number | 60 | Hands-on active service time |
processing | number | 0 | Processing / wait time |
finish | number | 0 | Finishing time |
turnover | number | 0 | Turnover / reset time |
ServiceBookingRules
| Field | Type | Default | Description |
|---|---|---|---|
onlineEnabled | boolean | true | Online booking enabled |
existingOnly | boolean | false | Only existing customers can book |
requiresConsult | boolean | false | Consultation required first |
maxDaysOut | number | 30 | Max days in advance |
minNoticeHours | number | 0 | Minimum booking notice (hours) |
lateCancelHours | number | 24 | Late cancellation threshold (hours) |
ServiceAvailability
| Field | Type | Default | Description |
|---|---|---|---|
mode | enum | INHERIT | ALWAYS, SCHEDULED, or INHERIT |
weeklySchedule | object | — | Weekly schedule (required when SCHEDULED) |
dateRanges | array | — | Seasonal availability or blackout periods |
Enums
| Enum | Values |
|---|---|
ServicePriceMode | FIXED, STARTS_AT, VARIABLE |
ServiceGratuityMode | NONE, OPTIONAL, REQUIRED |
ServiceAvailabilityMode | ALWAYS, SCHEDULED, INHERIT |
ServiceDepositStrategy | NONE, FIXED, PERCENTAGE |
Organize related services into categories for cleaner navigation, and use duration segments and buffers to model real chair/room time and prevent scheduling conflicts.