Technical Step-by-Step Instructions: Creating or Updating a Wix Bookings Service (Real-World, API-First)
Description
Below are the recommended steps to successfully create or update a Wix Bookings service (or several at once) on Wix, with real-world troubleshooting and fixes for common API issues.
Prerequisites
- Wix Bookings app installed (App ID:
13d21c63-b5ec-5912-8397-c3a5ddb27a97)
Note: If you receive errors from Bookings APIs, the Wix Bookings app may not be installed on the site. Use List Installed Apps to verify, and Install Wix Apps to install it if missing.
Overview
A Bookings service defines a time based offering and includes the following considerations:
- type - for detailed information about service type - refer to the article
APPOINTMENT- Appointments allow customers to book services at their preferred time during the business hours. For example, a hair salon might offer different appointment-based hair cutting and styling services. Appointments appear in the booking calendar once they're booked by a customer. Not-yet-booked times during the business hours are displayed as available slots to potential customers while booking. The availability of the service is based on the availability of the staff member providing itCLASS- A class is a single event or a series of recurring events that multiple customers can book. For example, a yoga studio might offer a twice-weekly vinyasa flow class. Classes may have a set end date or continue indefinitely. If a class includes more than a single event, customers can sign up for 1, several, or all of the events. Upon creation, classes are listed immediately in the booking calendar.COURSE- A course starts and ends on pre-defined dates with a limited number of events that multiple customers can book. For example, a yoga studio might offer a teacher training course with 5 events. In contrast to classes, customers must book the entire course. Upon creation, courses are displayed immediately in the booking calendar.- Staff Member - a resource required in order to provide a service. REST A staff member availability is defined by the schedule associated with the staff member, by default it is the main business schedule and cannot be modified by schedule APIs, but a staff member can have its own schedule by calling
assignWorkingHoursSchedulewhich allows the staff member to have its own availability. The propertystaffMember.usesDefaultWorkingHoursdefines whether the default hours (business hours) are used. - Schedule (availability) - availability is defined by
Events(REST) defined on a schedule. - The schedule which defines the service availability is based on the service type.
- For appointment service, it is based on the schedule of staff members which provides it (
service.staffMemberIdswhich is mapped tostaffMember.resourceId). In order to fetch the staff schedule you should retrieve the staff member withRESOURCE_DETAILSfieldmask and read the schedule id from thestaffMember.resource.eventsSchedule.id- This is needed if you wish to define the staff member's availability as part of the process - For classes and courses it is based on the schedule of the service itself (
service.schedule) - When creating an APPOINTMENT service and specifying
staffMemberIds, ensure you are using the staff member's resourceId, not their primary staff member id. - Service Images - the service may have several images -
service.media.mainMedia- presented in the services list,service.media.coverMedia- presented in the service page andservice.media.items- array of images presented as a gallery in the service page for site visitors. - In order to add a media (image) to a service, it should first be defined in Wix Media Manager - search existing (REST) or new (REST)
- Set only the image id, but nest it inside the media item's
imageobject:service.media.mainMedia.image.id(likewiseservice.media.coverMedia.image.idandservice.media.items[].image.id). Theidis the binding field;urland dimensions are descriptive and need not be set. ⚠️ Setting the id directly on the media item (service.media.mainMedia.id, without theimagewrapper) returns HTTP 200 but silently drops it — the image reads back empty (image.id: ""). Because it's a silent 200, a success status is not proof: always nest under.imageand confirm with a re-query.
Service Type Selection Guide
Choose based on these documented behaviors:
- APPOINTMENT: Customer picks available time slot. Availability based on staff schedules. One customer (or dedicated group) per booking.
- CLASS: Business sets recurring times. Multiple customers book same session. Customers can book 1, some, or all sessions in series.
- COURSE: Business sets fixed series. Multiple customers book. Customers must book entire course (all sessions).
When unsure, refer to About Service Types.
CRITICAL: Staff Assignment Behavior by Service Type
APPOINTMENT Services:
- Staff assignment WORKS: Can specify
staffMemberIdsarray with staff memberresourceIdvalues - Behavior: Service availability based on assigned staff schedules
- Example: Personal training session assigned to specific trainer
CLASS and COURSE Services:
- Staff assignment IGNORED: Setting
staffMemberIdshas no effect on service creation - Behavior: Service uses its own schedule (
service.schedule), not staff schedules - Workaround: Staff association must be handled separately through calendar events or other mechanisms
- Example: Yoga class where any qualified instructor can teach
This is a critical API limitation that affects service planning and staff resource management.
IMPORTANT NOTES
- I MUST read the full articles about service types, service payments, service location in order to fully understand how to set the service properties
- If the service type is
CLASSorCOURSEI MUST read the full articles service's schedule and events mentioned before - If the service type is
APPOINTMENTI MUST read the relevant full article about staff members (REST) in order to determine whether I should create a new staff member (or members) - For free service I MUST set
service.payment.rateTypeas"NO_FEE"andservice.payment.options.inPersonastrue(at least one payment option must be true) - For paid service I MUST set the
service.payment.fixed.price.value(must be above 0) as well asservice.payment.fixed.price.currency - When changing a free service to a paid service, I MUST update
service.payment.rateTypefrom"NO_FEE"to"FIXED"in the same request where I setservice.payment.fixed.price; patching onlyfixed.priceon aNO_FEEservice fails validation.
Payment Options Validation Rules
| rateType | options.online |
options.inPerson |
Valid? |
|---|---|---|---|
| FIXED | true | false | ✓ |
| FIXED | false | true | ✓ |
| FIXED | true | true | ✓ |
| VARIED | true | false | ✓ |
| VARIED | false | true | ✓ |
| NO_FEE | false | true | ✓ |
| NO_FEE | true | false | ✗ (online not allowed for NO_FEE) |
| Any | false | false | ✗ (at least one must be true) |
- Always Prioritize Reading Full API Method Documentation: this overview article provides a general workflow. However, it repeatedly stresses the importance of reading the full documentation for each specific REST method you intend to use. This is critical for understanding detailed requirements.
- I should pay close attention to all required fields, data types, enum values, and specific ID types (e.g., resourceId vs. id) as defined in the detailed schema of each API endpoint. The overview article serves as a guide but doesn't replace the need to consult these specifics.
Service Categories - CRITICAL for UI Visibility
IMPORTANT: Services without categories may not appear in category-based UI filters, which are commonly used in booking interfaces.
Service Category Considerations:
- Default Behavior: Services created without explicit category assignment may not be visible in filtered views
- UI Impact: Many booking interfaces filter services by
category.id, hiding uncategorized services - Best Practice: Always assign services to appropriate categories during creation
Category Management Steps:
- Query existing categories using Query Categories to see available options
- Create new category if needed using Create Category
- Assign category during service creation by including
category.idin the service object - Update existing services using Update Service to add missing categories
Common Category Filter Patterns:
{
"filter": {
"category.id": {
"$exists": true
}
}
}This filter will only show services with assigned categories, making uncategorized services invisible to users.
Querying Existing Services
You can retrieve a list of existing booking services using the Query Services endpoint. This allows you to filter, sort, and page through up to 100 services at a time, making it easy to find and manage your current offerings.
Steps
0. Query and Setup Categories (CRITICAL FIRST STEP)
- Query existing categories using
queryCategoriesAPI (REST) to identify available categories - Create category if needed using
createCategoryAPI (REST) if no suitable category exists - Record category ID for use in service creation - this prevents services from being hidden in UI filters
Query Categories:
curl -X POST 'https://www.wixapis.com/bookings/v2/categories/query' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{ "query": {} }'Create Category (if none exist):
curl -X POST 'https://www.wixapis.com/bookings/v2/categories' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{ "category": { "name": "General" } }'1. Define staff member to use (REQUIRED for APPOINTMENT)
IMPORTANT: For APPOINTMENT services,
staffMemberIdsis required. The API will return a 400 error without it. You must query staff members first to obtain a validresourceId.
- Query existing staff members to get their
resourceIdvalues - For new staff, create using
createStaffMemberAPI (REST) and keep the responsestaffMember.idandresourceId - If you wish to update working hours, call
getStaffMemberAPI (REST) to getresource.eventsSchedule.id
Query Staff Members (to get resourceId):
curl -X POST 'https://www.wixapis.com/bookings/v1/staff-members/query' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"query": {},
"fields": ["RESOURCE_DETAILS"]
}'Use the resourceId from the response (not id) in staffMemberIds when creating APPOINTMENT services.
Staff Selection Strategy:
- If a staff member has
default: true→ use it - If only one staff member exists → use it
- If multiple exist → pick the first or most appropriate
- If none exist → create one using the Staff Setup recipe
Service Type Requirements:
- APPOINTMENT:
staffMemberIdsis required - API will fail without it - CLASS/COURSE:
staffMemberIdsis ignored; useservice.scheduleinstead
2. Creating or Updating a service
Based on the information gathered above, use the relevant API based on the desired outcome.
- Create services:
bulkCreateServicesendpoint (REST) - Update single service:
updateServiceendpoint (REST) - Update services (bulk):
bulkUpdateServices(REST) - Update by filter:
bulkUpdateServicesByFilter(REST) - Get single service:
getServiceendpoint (REST)
Create Service Example (paid APPOINTMENT, 60 minutes):
curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"returnEntity": true,
"services": [{
"name": "Consultation",
"type": "APPOINTMENT",
"onlineBooking": { "enabled": true },
"staffMemberIds": ["<RESOURCE_ID_FROM_STEP_1>"],
"schedule": {
"availabilityConstraints": {
"sessionDurations": [60]
}
},
"payment": {
"rateType": "FIXED",
"options": { "online": true, "inPerson": false },
"fixed": {
"price": { "value": "50", "currency": "USD" }
}
},
"category": {
"id": "<CATEGORY_ID_FROM_STEP_0>"
}
}]
}'Note: Currency may default to the site's business currency regardless of what you specify. Verify the response if currency is critical.
Create Service Example (free APPOINTMENT, 60 minutes):
curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"returnEntity": true,
"services": [{
"name": "Free Consultation",
"type": "APPOINTMENT",
"onlineBooking": { "enabled": true },
"staffMemberIds": ["<RESOURCE_ID_FROM_STEP_1>"],
"schedule": {
"availabilityConstraints": {
"sessionDurations": [60]
}
},
"payment": {
"rateType": "NO_FEE",
"options": { "online": false, "inPerson": true }
},
"category": {
"id": "<CATEGORY_ID_FROM_STEP_0>"
}
}]
}'Create Service Example (CLASS with capacity):
Note: CLASS services do not use
staffMemberIdsorsessionDurations. After creation, you must create events viabulkCreateEventsusing the returnedservice.schedule.idto define when the class occurs.
curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"returnEntity": true,
"services": [{
"name": "Yoga Class",
"type": "CLASS",
"onlineBooking": { "enabled": true },
"defaultCapacity": 20,
"payment": {
"rateType": "FIXED",
"options": { "online": true, "inPerson": false },
"fixed": {
"price": { "value": "25", "currency": "USD" }
}
},
"category": {
"id": "<CATEGORY_ID_FROM_STEP_0>"
}
}]
}'After creation, use results[0].item.schedule.id from the response to create class events with bulkCreateEvents (see Step 3). This requires returnEntity: true on the request — without it the response carries only results[0].itemMetadata.id, which has no schedule.id; and the created service is directly under item (there is no item.service).
Required Fields:
name- Service nametype-APPOINTMENT,CLASS, orCOURSEonlineBooking: { enabled: true }- Required for all servicesstaffMemberIds- Required for APPOINTMENT only (useresourceIdvalues); ignored for CLASS/COURSEschedule.availabilityConstraints.sessionDurations- Duration in minutes (APPOINTMENT only)defaultCapacity- Required for CLASS/COURSE (max participants per session)payment.options- At least one ofonlineorinPersonmust betrue(required for all services, including free; see validation table above)
Service Type Specific Considerations:
- APPOINTMENT: Must include
staffMemberIdswith staffresourceIdvalues - CLASS/COURSE: Omit
staffMemberIds; configureservice.scheduleinstead
Update Service Example (PATCH):
Note: Updates require the current
revisionvalue (from a GET response) placed inside theserviceobject, not at the top level.
# First, get current service to obtain revision
curl -X GET 'https://www.wixapis.com/bookings/v2/services/<SERVICE_ID>' \
-H 'Authorization: <AUTH>'
# Then update with revision inside service object
curl -X PATCH 'https://www.wixapis.com/bookings/v2/services/<SERVICE_ID>' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"service": {
"revision": "<REVISION_FROM_GET>",
"category": {
"id": "<CATEGORY_ID>"
}
}
}'Update free service to fixed price:
When an existing service has payment.rateType: "NO_FEE" and the user asks to set a price, convert it to FIXED and set the price in the same update.
# First, get current service to obtain revision and current payment settings
curl -X GET 'https://www.wixapis.com/bookings/v2/services/<SERVICE_ID>' \
-H 'Authorization: <AUTH>'
curl -X PATCH 'https://www.wixapis.com/bookings/v2/services/<SERVICE_ID>' \
-H 'Authorization: <AUTH>' \
-H 'Content-Type: application/json' \
-d '{
"service": {
"revision": "<REVISION_FROM_GET>",
"payment": {
"rateType": "FIXED",
"options": { "online": false, "inPerson": true },
"fixed": {
"price": { "value": "200", "currency": "<SITE_CURRENCY>" }
}
}
}
}'3. Set the availability of the service
Once the service and staff member are available, you can define when the service is available:
3a. Determine the schedule to use based on the service type:
- APPOINTMENT: The staff member working hours determine the service availability. If the staff member needs different hours from the business defaults, call
assignWorkingHoursSchedule(POST https://www.wixapis.com/bookings/v1/staff-members/<STAFF_MEMBER_ID>/assign-working-hours-schedule) (REST). Use theresource.eventsSchedule.idas thescheduleId. - CLASS or COURSE: Use the
service.schedule.idfrom the service created/updated in Step 2.
3b. Create events using bulkCreateEvents (POST https://www.wixapis.com/calendar/v3/bulk/events/create) (REST) or update existing ones with bulkUpdateEvents (POST https://www.wixapis.com/calendar/v3/bulk/events/update) (REST).
Event requirements:
event.resourcesarray must include at least one resource (a staff member/room/etc.) using theresourceId. CLASS and COURSE events will fail with a 400 error if no resources are provided.event.scheduleId— use the staff member's events schedule ID for APPOINTMENT availability, orservice.schedule.idfor CLASS/COURSE.event.type— set toWORKING_HOURSfor staff availability,CLASSfor class sessions, orCOURSEfor course sessions.
Troubleshooting Common Issues
APPOINTMENT Service Creation Fails (staffMemberIds required):
- Error:
"service of type appointment requires at least one staff member id" - Cause: APPOINTMENT services cannot be created without at least one staff member assigned
- Solution: Query staff members first (Step 1) to get a valid
resourceId, then include it instaffMemberIds
Service Creation Fails (payment.options required):
- Error:
INVALID_PAYMENT_OPTIONS - "It is mandatory to specify either payment.options.online or payment.options.inPerson as true" - Cause: All services (including free) require at least one payment option to be
true - Solution: Add
"options": { "online": true, "inPerson": false }(orinPerson: truefor free services) to thepaymentobject
Free Service Fails with online=true:
- Error:
INVALID_PAYMENT_OPTIONS - "Specifying payment.paymentOptions.online as true is applicable only to payments of types FIXED or VARIED" - Cause:
payment.options.online: trueis only valid for paid services (FIXED or VARIED) - Solution: For free services (NO_FEE), use
"options": { "online": false, "inPerson": true }
Changing a free service price fails:
- Error:
"Payment of type FREE cannot be used with payment.rate" - Cause: The service is still
NO_FEEwhile the update tries to setfixed.price - Solution: Change
payment.rateTypeto"FIXED"and includepayment.fixed.pricein the same update request
Services Not Appearing in UI Filters:
- Problem: Services created without category assignment are invisible in category-based filters
- Root Cause: Many UI implementations filter by
category.idexistence or specific category values - Solution: Query all services, identify those missing categories, and update them using bulk update operations
- Prevention: Always assign categories during service creation (Step 0)
Staff Assignment Not Working for CLASS/COURSE Services:
- Problem: Setting
staffMemberIdsin CLASS or COURSE services appears to be ignored - Solution: This is expected behavior; use service schedules instead of staff assignments
- Alternative: Manage staff-to-class relationships through calendar events or custom data structures
App Not Installed Errors:
- Problem: 428 "App not installed" errors when creating services
- Solution: Install Wix Bookings app using Apps Installer API before creating services
- Verification: Query existing services to confirm app installation
Resource ID vs Staff ID Confusion:
- Problem: Using wrong ID type for
staffMemberIdsarray - Solution: Always use
staffMember.resourceId, notstaffMember.id - Verification: Query staff with
RESOURCE_DETAILSfieldMask to get correct IDs
Service Schedule vs Staff Schedule Confusion:
- Problem: Mixing up schedule IDs between service and staff schedules
- Solution:
- APPOINTMENT: Use staff schedule ID (
staffMember.resource.eventsSchedule.id) - CLASS/COURSE: Use service schedule ID (
service.schedule.id)
- APPOINTMENT: Use staff schedule ID (
Update Service Fails with revision error:
- Error:
revision must not be emptyorservice.revision is required - Cause:
revisionplaced at top level of request body instead of insideserviceobject - Solution: Structure as
{ "service": { "revision": "...", ...fields } }- get revision value from GET response first
IMPORTANT NOTES
- I MUST read the full article about the REST method I wish to use
onlineBookingField: The onlineBooking object (e.g., {"enabled": true}) is a required field when creating services, even if not explicitly highlighted as mandatory in the high-level overview. This is a schema-level requirement.- Event Creation (BulkCreateEvents): When specifying recurrenceRule.days, I MUST use full day names (e.g., "TUESDAY", "FRIDAY")
- The recurrenceRule.days field within an event object can only accept a single day of the week (e.g., ["TUESDAY"]).
- If I need to set up recurring events for multiple days of the week (e.g., a staff member working every Tuesday and Friday), I MUST define a separate event for each day and send them as separate items for BulkCreateEvents.
- Start Dates for Recurring Events: Recurring events must have a start.localDate that is today or in the future, relative to the server's current time. If I am not sure what the current date and time are I MUST check it.
- When setting
event.typeasWORKING_HOURS(APPOINTMENT) I MUST callassignWorkingHoursScheduleBEFORE CREATING THE EVENTS so that the staff member is no longer linked to the business working hours - When setting
event.typeasCLASSorCOURSEI MUST use the service schedule id, so the service has to exist (created/updated) before setting the availability of it
Booking REST API Documentation Reference
- Query Categories
- Create Category
- Get Service
- Update Service
- Create Staff Member
- Get Staff Member
- Query Staff Members
- Bulk Create Services
- Bulk Update Services
- Bulk Update Services By Filter
- Assign Working Hours Schedule
- Bulk Create Events
- Bulk Update Events
- Media Manager: Search Files
- Media Manager: Bulk Import File
- Query Services
- Apps Installer API