@wix/agent-skills 1.18.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "Booking Service Policy Setup"
3
- description: Sets up booking policies, cancellation rules, and waitlist configuration using the Services API policy fields. Covers bookingPolicy, cancellationPolicy, and waitlist settings.
3
+ description: Sets up booking policies, cancellation rules, and waitlist configuration using the Booking Policies API (query, then PATCH the bookingPolicy entity with its revision). Covers cancellationPolicy, reschedulePolicy, booking-notice limits, waitlistPolicy, and participants limits — e.g. "customers can cancel up to 24 hours before".
4
4
  ---
5
5
 
6
6
  # Technical Step-by-Step Instructions: Configuring Wix Bookings Service Policies (Real-World, API-First)
@@ -20,72 +20,98 @@ Wix Bookings policy configuration allows businesses to set rules for:
20
20
  - **Waitlist policies**: When waitlists are enabled, capacity handling
21
21
  - **Group booking policies**: Maximum participants per booking
22
22
 
23
- Policies can be configured at two levels:
23
+ Policies are **standalone entities** managed by the Booking Policies API (`/bookings/v1/booking-policies`), not fields nested on the service:
24
24
 
25
- - **Site-wide (business) policies**: Default rules that apply to all services
26
- - **Service-specific policies**: Override rules for individual services
25
+ - Installing Wix Bookings creates a **"Default policy"** (`default: true`) that every new service references.
26
+ - Each service points at exactly one policy via its `bookingPolicy` field; many services can share a policy.
27
+ - To change rules site-wide, update the default policy. To give one service different rules, create a separate policy and reference it from that service.
27
28
 
28
29
  ### IMPORTANT NOTES
29
30
 
30
- - Services inherit from site-wide booking policies by default
31
- - Service policies only override fields you explicitly specify - unspecified fields keep site defaults
32
- - Policy options available may vary based on service type (APPOINTMENT, CLASS, COURSE)
33
- - All policy configurations support the same core features across service types
31
+ - A default policy exists on any site with Wix Bookings installed — **even before any service exists**. "Set a cancellation window" is always actionable by updating the default policy.
32
+ - Policy updates are PATCH-style: send only the sub-policies you want to change, plus the current `revision` (fetched via query/get) inside the `bookingPolicy` object.
33
+ - Policy options are the same across service types (APPOINTMENT, CLASS, COURSE).
34
34
 
35
35
  ---
36
36
 
37
37
  ## Steps
38
38
 
39
- ### 1. Configure Site-Wide Business Policy (Optional)
39
+ ### 1. Find the Policy to Change
40
40
 
41
- Set default policies that will apply to all services unless overridden. Use the Business Policy API to configure booking deadlines, cancellation rules, waitlist settings, and group booking limits.
41
+ Query existing policies (`POST https://www.wixapis.com/bookings/v1/booking-policies/query`):
42
42
 
43
- ### 2. Create or Update Service with Policy Overrides
43
+ ```json
44
+ { "query": { "cursorPaging": { "limit": 100 } } }
45
+ ```
44
46
 
45
- When creating or updating a service, specify policy fields that should differ from business defaults. Only include the policy fields you want to override - unspecified fields will inherit from business defaults.
47
+ For a site-wide change, pick the policy with `"default": true`. For a specific service, read the service first — its embedded `bookingPolicy.id` names the policy it uses. Save the policy's `id` and `revision`.
46
48
 
47
- ### 3. Configure Course-Specific Policies
49
+ ### 2. Update the Policy
48
50
 
49
- Courses may have additional policy options such as `bookUntilXMinutesAfterStart` which allows customers to join courses even after they've started.
51
+ `PATCH https://www.wixapis.com/bookings/v1/booking-policies/<POLICY_ID>` with the current `revision` inside the `bookingPolicy` object and only the sub-policies you're changing. Example — cancellations allowed up to 24 hours before the session:
52
+
53
+ ```json
54
+ {
55
+ "bookingPolicy": {
56
+ "id": "<POLICY_ID>",
57
+ "revision": "<CURRENT_REVISION>",
58
+ "cancellationPolicy": {
59
+ "enabled": true,
60
+ "limitLatestCancellation": true,
61
+ "latestCancellationInMinutes": 1440
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ **Field map (business rule → policy field):**
68
+
69
+ | Business rule | Sub-policy fields |
70
+ |---|---|
71
+ | Cancellation deadline | `cancellationPolicy.{enabled, limitLatestCancellation, latestCancellationInMinutes}` |
72
+ | Reschedule deadline | `reschedulePolicy.{enabled, limitLatestReschedule, latestRescheduleInMinutes}` |
73
+ | How far ahead customers can book | `limitEarlyBookingPolicy.{enabled, earliestBookingInMinutes}` |
74
+ | Booking notice / last-minute cutoff | `limitLateBookingPolicy.{enabled, latestBookingInMinutes}` |
75
+ | Booking after session start (courses/classes) | `bookAfterStartPolicy.enabled` |
76
+ | Waitlist | `waitlistPolicy.{enabled, capacity, reservationTimeInMinutes}` |
77
+ | Participants per booking | `participantsPolicy.{enabled, maxParticipantsPerBooking}` |
78
+
79
+ ### 3. Per-Service Policies (Optional)
80
+
81
+ To give one service different rules, create a new policy (`POST https://www.wixapis.com/bookings/v1/booking-policies`) and point the service at it by updating the service's `bookingPolicy` reference (see [Update Service](https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/services-v2/update-service)).
50
82
 
51
83
  ### 4. Verify Policy Application
52
84
 
53
- Query the service to confirm policies are applied correctly. The service should show explicitly set policy fields with your specified values and unspecified fields inheriting from business defaults.
85
+ Read the service (`GET /bookings/v2/services/<SERVICE_ID>`) — the response embeds the full resolved `bookingPolicy`, so you can confirm the effective rules exactly as customers will experience them.
54
86
 
55
87
  ### IMPORTANT NOTES
56
88
 
57
- - **Policy inheritance**: Only specify fields you want to override - leave others undefined to inherit business defaults
58
- - **Partial updates**: When updating service policies, only include fields you want to change
59
- - **Capacity requirements**: Waitlist policies require service capacity settings to be configured
60
- - **Group booking considerations**: `maxParticipantsPerBooking` works with all service types
61
- - **Time calculations**: Policy deadlines are calculated from booking start time in business timezone
62
- - **Course flexibility**: Course services support additional policy options like `bookUntilXMinutesAfterStart`
89
+ - **Revision required**: Every policy update needs the current `revision` inside `bookingPolicy`; fetch it first, or the PATCH fails.
90
+ - **Partial updates**: Send only the sub-policies you're changing; the rest keep their values.
91
+ - **Enabled flags matter**: Each sub-policy has an `enabled` (and often a `limit*`) boolean — setting only the minutes value without enabling the limit has no effect.
92
+ - **Time calculations**: Policy deadlines are calculated from the booking's start time, in minutes (24 hours = 1440).
63
93
 
64
94
  ### Troubleshooting Common Issues
65
95
 
66
96
  **Policies not applying:**
67
97
 
68
- - Verify you're setting policies on the service object, not as separate policy entities
69
- - Check that policy fields are properly nested under `service.policy`
70
- - Ensure you're updating the correct service ID
98
+ - Policies are separate entities — verify you PATCHed `/bookings/v1/booking-policies/<id>`, not fields on the service object (there is no `service.policy` field)
99
+ - Confirm the service actually references the policy you changed (`service.bookingPolicy.id`)
100
+ - Check the `revision` was current — a stale revision fails the update
71
101
 
72
102
  **Waitlist not working:**
73
103
 
74
- - Confirm service has `maxParticipants` capacity set
75
- - Verify `waitingListPolicy.enabled` is `true`
76
- - Check that `waitingListPolicy.capacity` is set if you want limited waitlist size
104
+ - Verify `waitlistPolicy.enabled` is `true` and `waitlistPolicy.capacity` is set
105
+ - Confirm the service has capacity configured (`defaultCapacity` for CLASS/COURSE)
77
106
 
78
107
  **Cancellation policies not enforced:**
79
108
 
80
- - Ensure `cancelRescheduleUpToInMinutes` is set to appropriate value
81
- - Verify `cancelationAllowed` is `true` if cancellations should be permitted
82
- - Check that payment and booking flow supports the configured policy
109
+ - Set all three fields together: `cancellationPolicy.enabled: true`, `limitLatestCancellation: true`, and `latestCancellationInMinutes` — the minutes value alone has no effect without the booleans
83
110
 
84
111
  **Group booking limits not working:**
85
112
 
86
- - Confirm `maxParticipantsPerBooking` is set to desired limit
87
- - Verify service type supports group bookings (all types do)
88
- - Check that booking UI respects the participant limit
113
+ - Confirm `participantsPolicy.enabled` is `true` and `participantsPolicy.maxParticipantsPerBooking` is set
114
+ - Check that the booking UI respects the participant limit
89
115
 
90
116
  ## API Documentation References
91
117
 
@@ -68,7 +68,8 @@ The most common mistake is confusing which ID to use in which API call, leading
68
68
 
69
69
  * **Two-Step Requirement**: Custom working hours requires both `assignWorkingHoursSchedule` AND creating `WORKING_HOURS` events. Only doing the first step results in staff with no actual working hours.
70
70
  * **Foundation Dependencies**: Staff operations require Wix Bookings app installation and proper business schedule configuration. "Business schedule not found" errors indicate missing foundation setup.
71
- * **Event Field Requirements**: When creating `WORKING_HOURS` events, several fields that appear optional in documentation are actually required: `externalScheduleId`, `adjustedStart`, `adjustedEnd`, `resources` array, and `appId`.
71
+ * **Bulk Request Envelope**: `bulkCreateEvents` requires each event to be wrapped in an `event` object — `{"events": [{"event": {...}}], "returnEntity": true}`. Sending a flat `{"events": [{...}]}` fails with 400 `event must not be empty`. Pass `returnEntity: true` to get the created events back; otherwise the response contains only `itemMetadata`.
72
+ * **Minimal Event Fields**: A `WORKING_HOURS` event needs only `type`, `scheduleId`, `start.localDate`, `end.localDate`, and (for recurring hours) `recurrenceRule`. The server derives `externalScheduleId` (set to the staff member's resource ID automatically), `adjustedStart`/`adjustedEnd`, and `appId`; `resources` may stay empty on a working-hours schedule.
72
73
  * **Revision Numbers**: Event updates ALWAYS require the current revision number. Fetch current event details before any update operation.
73
74
  * **Time Format Precision**: All date/time fields must use precise ISO 8601 format (`YYYY-MM-DDTHH:mm:ss`) without timezone indicators.
74
75
  * **Separate Events for Each Day**: Each working day requires a separate event with single-day recurrence rules. You cannot specify multiple days in one event's `recurrenceRule.days` array.
@@ -113,9 +114,7 @@ Before creating staff members, ensure the foundation is properly configured:
113
114
 
114
115
  **Install Wix Bookings App** (if needed): Use the App Installer API with Wix Bookings app ID: `13d21c63-b5ec-5912-8397-c3a5ddb27a97`.
115
116
 
116
- **Configure Business Schedule**: Set up site properties with business operating hours using the Site Properties API.
117
-
118
- **Create Calendar Schedule and Working Hours**: Create a calendar schedule for Wix Bookings integration and populate it with business `WORKING_HOURS` events.
117
+ **Configure Business Schedule**: Default business hours live as `WORKING_HOURS` calendar events on the business schedule — set them up per the [Configure Default Business Hours](../calendar/configure-default-business-hours.md) recipe. Do **not** use the Site Properties API for this: its business-schedule fields are separate general site info and do not drive Bookings availability.
119
118
 
120
119
  ### 2. Verify Current Business Hours (CRITICAL)
121
120
 
@@ -156,11 +155,10 @@ Create staff members using the Staff API (`POST https://www.wixapis.com/bookings
156
155
 
157
156
  **⚠️ INHERITANCE MOMENT**: The moment this API call completes, the staff member inherits the current business hours. There's no "undo" for this inheritance.
158
157
 
159
- **Save Critical IDs from Response**:
160
- - `staffMember.id` for updates and schedule assignment
161
- - `staffMember.resourceId` for services and event resources
162
- - `staffMember.resource.eventsSchedule.id` for working hours events
163
- - `staffMember.resource.usesDefaultWorkingHours` (should be `true` initially)
158
+ **Save Critical IDs**:
159
+ - `staffMember.id` (in the create response) for updates and schedule assignment
160
+ - `staffMember.resourceId` (in the create response) for services and event resources
161
+ - `staffMember.resource.eventsSchedule.id` for working hours events — **NOT in the create response**. The create response contains only id/name/resourceId; fetch the staff member again with `?fields=RESOURCE_DETAILS` to get `resource.eventsSchedule.id` (and `resource.usesDefaultWorkingHours`, initially `true`). Reading `resource.eventsSchedule.id` off the create response yields `undefined` and downstream `scheduleId must not be empty` errors.
164
162
 
165
163
  ### 5. Set Up Custom Working Hours (Two-Step Process)
166
164
 
@@ -168,21 +166,44 @@ Create staff members using the Staff API (`POST https://www.wixapis.com/bookings
168
166
 
169
167
  **Step 5A: Assign Custom Working Hours Schedule**
170
168
 
171
- Get staff member details (`GET https://www.wixapis.com/bookings/v1/staff-members/<STAFF_MEMBER_ID>?fields=RESOURCE_DETAILS`) to extract the events schedule ID. Call `assignWorkingHoursSchedule` (`POST https://www.wixapis.com/bookings/v1/staff-members/<STAFF_MEMBER_ID>/assign-working-hours-schedule`) using the staff member ID and their events schedule ID. This detaches the staff member from business default hours.
169
+ Get staff member details (`GET https://www.wixapis.com/bookings/v1/staff-members/<STAFF_MEMBER_ID>?fields=RESOURCE_DETAILS`) to extract the events schedule ID. Then call `assignWorkingHoursSchedule` (`POST https://www.wixapis.com/bookings/v1/staff-members/<STAFF_MEMBER_ID>/assign-working-hours-schedule`):
170
+
171
+ ```json
172
+ { "scheduleId": "<EVENTS_SCHEDULE_ID>" }
173
+ ```
174
+
175
+ The request field is `scheduleId` (other names, e.g. `eventsScheduleId`, fail with 400 `scheduleId must not be empty`). This detaches the staff member from business default hours.
172
176
 
173
177
  Verify the response shows `"usesDefaultWorkingHours": false`.
174
178
 
179
+ > **On failure, resume — don't restart.** If this call (or a later step) fails, the staff member from Step 4 already exists. Fix the failing call and continue with the same staff member ID; re-running the whole flow creates a duplicate staff member.
180
+
175
181
  **Step 5B: Create WORKING_HOURS Events**
176
182
 
177
- Use the Events API (`POST https://www.wixapis.com/calendar/v3/bulk/events/create`) to create working hours events for each working day. Each event must include:
178
- - `scheduleId`: Staff member's events schedule ID
179
- - `externalScheduleId`: Staff member's resource ID
180
- - `type`: `"WORKING_HOURS"`
181
- - `resources` array: Staff member's resource ID
182
- - `appId`: Wix Bookings app ID
183
- - Proper recurrence rules with single-day specification
183
+ Use the Events API (`POST https://www.wixapis.com/calendar/v3/bulk/events/create`) to create one recurring event per working day. Note the request envelope: each event is wrapped in an `event` object.
184
184
 
185
- Create separate events for each day of the week the staff member works.
185
+ ```json
186
+ {
187
+ "events": [
188
+ {
189
+ "event": {
190
+ "type": "WORKING_HOURS",
191
+ "scheduleId": "<STAFF_EVENTS_SCHEDULE_ID>",
192
+ "start": { "localDate": "2026-09-07T09:00:00" },
193
+ "end": { "localDate": "2026-09-07T15:00:00" },
194
+ "recurrenceRule": { "frequency": "WEEKLY", "interval": 1, "days": ["MONDAY"] }
195
+ }
196
+ }
197
+ ],
198
+ "returnEntity": true
199
+ }
200
+ ```
201
+
202
+ - `scheduleId` is the staff member's **events schedule ID** (`resource.eventsSchedule.id`).
203
+ - `externalScheduleId`, `adjustedStart`/`adjustedEnd`, and `appId` are derived by the server — you do not need to send them (`externalScheduleId` comes back set to the staff member's resource ID).
204
+ - `recurrenceRule.frequency` must be `WEEKLY` (the only supported value); `interval` is 1–4.
205
+ - `recurrenceRule.days` takes exactly one **full uppercase** day name from `MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY` — abbreviations like `MON` fail validation. One day per event, so create a separate event for each working day.
206
+ - `start`/`end` must be today or in the future for recurring events.
186
207
 
187
208
  ### 6. Verify Setup
188
209
 
@@ -193,10 +214,9 @@ Query the staff member to confirm:
193
214
 
194
215
  ### IMPORTANT NOTES
195
216
 
196
- * **ID Relationship Critical**: Use staff member ID for assignment, events schedule ID for event `scheduleId`, and resource ID for `externalScheduleId` and `resources` array
217
+ * **ID Relationship Critical**: Use the staff member ID for assignment and the events schedule ID for event `scheduleId`; the resource ID appears in server-derived fields (`externalScheduleId`) and in `resources` arrays when attaching this staff member to CLASS/COURSE session events
197
218
  * **Foundation First**: Complete foundation setup before staff operations to avoid "Business schedule not found" errors
198
219
  * **Complete Two-Step Process**: Both schedule assignment AND event creation are mandatory for custom working hours
199
- * **Event Field Requirements**: Include all required fields even if they appear optional in basic documentation
200
220
  * **Revision Management**: Always fetch current revision numbers before updating events
201
221
  * **Date Format Strict**: Use precise ISO 8601 format without timezone indicators
202
222
  * **Inheritance is Permanent**: Once inherited, business hours changes don't affect existing staff
@@ -218,7 +238,10 @@ Completed assignment but missing event creation. Create `WORKING_HOURS` events u
218
238
  - **Solution**: Standardize by configuring custom hours for all staff, or recreate staff after fixing business hours
219
239
 
220
240
  **"Invalid scheduleId" or "Resource not found" Errors**:
221
- Using wrong ID type in API calls. Verify you're using events schedule ID for `scheduleId`, resource ID for `externalScheduleId` and `resources` array, and staff member ID for updates.
241
+ Using wrong ID type in API calls. Verify you're using the events schedule ID for `scheduleId` and the staff member ID for updates.
242
+
243
+ **400 "event must not be empty" on bulkCreateEvents**:
244
+ The request body is missing the per-event wrapper. Each entry in `events` must be `{"event": {...}}`, not the event fields directly.
222
245
 
223
246
  **Event Update Fails with Revision Error**:
224
247
  Get current event details first and include the revision number in update requests.
@@ -227,7 +250,7 @@ Get current event details first and include the revision number in update reques
227
250
  Use current or future dates for recurring event start dates.
228
251
 
229
252
  **Events Not Linked to Staff Member**:
230
- Ensure `resources` array contains staff member's resource ID and `externalScheduleId` is properly set.
253
+ Verify the event was created on the staff member's own events schedule (`resource.eventsSchedule.id`) — the schedule is what links working-hours events to the staff member. The returned event's `externalScheduleId` should equal the staff member's resource ID (server-derived).
231
254
 
232
255
  ### Working with Existing Staff
233
256
 
@@ -18,8 +18,9 @@ Below are the recommended steps to successfully create Wix Bookings services tha
18
18
  Before creating multi-resource services, ensure the following requirements are met:
19
19
 
20
20
  1. **Wix Bookings** - Core app must be installed and configured
21
- 2. **Premium Plan** - Advanced resource management typically requires premium features
22
- 3. **Business Location** - At least one business location must be configured
21
+ 2. **Business Location** - At least one business location must be configured
22
+
23
+ > Resource type, resource, and resource-bound service creation succeed via the API without any premium-related error. If a plan-gating error does occur, it surfaces explicitly (e.g. a 403) — don't preemptively block on plan checks.
23
24
 
24
25
  ### App Installation Process
25
26
 
@@ -112,6 +113,12 @@ Before creating anything, plan your resource type structure. Each type should re
112
113
 
113
114
  Create resource types using `createResourceType` API (`POST https://www.wixapis.com/bookings/v2/resources/resource-types`) ([REST](https://dev.wix.com/docs/api-reference/business-solutions/bookings/resources/resource-types-v2/create-resource-type)):
114
115
 
116
+ ```json
117
+ { "resourceType": { "name": "Treatment Room" } }
118
+ ```
119
+
120
+ The response returns `resourceType.id` — save it for resource creation and the service's `serviceResources`.
121
+
115
122
  **Key Requirements:**
116
123
  - `name` must be unique across the site
117
124
  - Keep `name` descriptive but concise (appears in booking interface)
@@ -121,9 +128,23 @@ Create resource types using `createResourceType` API (`POST https://www.wixapis.
121
128
 
122
129
  ### 3. Create Individual Resources
123
130
 
124
- For each resource type, create the individual resource instances using `createResource` API (`POST https://www.wixapis.com/bookings/v2/resources`) ([REST](https://dev.wix.com/docs/api-reference/business-solutions/bookings/resources/resources-v2/create-resource)):
131
+ Create the resource instances of each type. For more than one (the usual case — e.g. Room A and Room B), use `bulkCreateResources` (`POST https://www.wixapis.com/bookings/v2/bulk/resources/create`) ([REST](https://dev.wix.com/docs/api-reference/business-solutions/bookings/resources/resources-v2/bulk-create-resources)); for a single one, `createResource` (`POST https://www.wixapis.com/bookings/v2/resources`) takes `{ "resource": { "name": ..., "typeId": ... } }`.
132
+
133
+ ```json
134
+ {
135
+ "resources": [
136
+ { "name": "Room A", "typeId": "<RESOURCE_TYPE_ID>" },
137
+ { "name": "Room B", "typeId": "<RESOURCE_TYPE_ID>" }
138
+ ],
139
+ "returnEntity": true
140
+ }
141
+ ```
142
+
143
+ Read each created resource's ID from `results[i].item.id` (with `returnEntity: true`) or `results[i].itemMetadata.id` (without it). **The resource is directly under `item` — there is no `item.resource.id`** (reading it throws `Cannot read properties of undefined`). Match items to your request by `itemMetadata.originalIndex`, and check `bulkActionMetadata.totalFailures` before proceeding.
125
144
 
126
- **Gap Not in Docs**: The docs don't clearly explain that `availableInAllLocations: true` is the simplest approach and that specific location configuration is complex and often unnecessary.
145
+ **⚠️ Payload shape**: the type reference is the flat `typeId` field (same on single and bulk). Sending a nested object (`"type": {"id": ...}`) fails with 400 `Unexpected value for StringValue`. A resource without `typeId` is created but is not bookable.
146
+
147
+ If you omit `locationOptions`, the resource defaults to `availableInAllLocations: true` — the simplest and usually correct configuration; specific location setup is complex and often unnecessary.
127
148
 
128
149
  **Advanced Configuration (Gap):**
129
150
  - Custom working hours override business location hours (this behavior isn't clearly documented)
@@ -133,17 +154,42 @@ For each resource type, create the individual resource instances using `createRe
133
154
 
134
155
  ### 4. Create Multi-Resource Service (Optional Connection)
135
156
 
136
- Create the service that optionally connects to multiple resource types using `bulkCreateServices` API ([REST](https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/services-v2/bulk-create-services)):
157
+ Create the service that optionally connects to multiple resource types using `bulkCreateServices` API (`POST https://www.wixapis.com/bookings/v2/bulk/services/create`) ([REST](https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/services-v2/bulk-create-services)). Note the endpoint path is `/bulk/services/create` (not `/services/bulk/create`, which 404s).
158
+
159
+ **Pick the service type, and mind the resource-specific required fields:**
160
+
161
+ - A **1-on-1, time-slot** service (e.g. a massage, a room rental by the hour) is an `APPOINTMENT`. When such a service is driven by a **resource instead of a staff member**, `primaryResourceType` (a flat resource-type GUID) is **required** — omitting it fails with 400 `primary_resource_type is required for appointment services without staff members`. APPOINTMENT also needs `schedule.availabilityConstraints.sessionDurations`.
162
+ - A **group** service (fixed capacity per session) is a `CLASS` (drop-in) or `COURSE` (whole series); these use `defaultCapacity` and do not require `primaryResourceType`.
163
+
164
+ Appointment example (one room per booking, no staff):
137
165
 
138
- **Service Resource Configuration:**
139
166
  ```json
140
- "serviceResources": [
141
- {"resourceType": {"id": "room-type-id"}},
142
- {"resourceType": {"id": "equipment-type-id"}},
143
- {"resourceType": {"id": "instructor-type-id"}}
144
- ]
167
+ {
168
+ "services": [{
169
+ "name": "Massage",
170
+ "type": "APPOINTMENT",
171
+ "onlineBooking": { "enabled": true },
172
+ "serviceResources": [
173
+ { "resourceType": { "id": "<ROOM_TYPE_ID>" } }
174
+ ],
175
+ "primaryResourceType": "<ROOM_TYPE_ID>",
176
+ "schedule": { "availabilityConstraints": { "sessionDurations": [60] } },
177
+ "payment": {
178
+ "rateType": "FIXED",
179
+ "options": { "online": true, "inPerson": false },
180
+ "fixed": { "price": { "value": "80" } }
181
+ }
182
+ }],
183
+ "returnEntity": true
184
+ }
145
185
  ```
146
186
 
187
+ For a group service, use `"type": "CLASS"` with `"defaultCapacity": <n>` instead of `primaryResourceType`/`sessionDurations`. Both types take `payment.options` (at least one of `online`/`inPerson` true) — required even for the resource-bound case.
188
+
189
+ Save the service ID from the response: `results[0].item.id` (with `returnEntity: true`), or `results[0].itemMetadata.id` (without it). **There is no `results[0].item.service.id`** — reading that path throws `Cannot read properties of undefined`.
190
+
191
+ **⚠️ Verify from the response, not from assumptions**: without `returnEntity: true` the bulk response contains only `results[0].itemMetadata` (id + success flag) — there is no `item`. With the flag, the created service is at `results[0].item` (e.g. `item.id`, `item.serviceResources`). Check `bulkActionMetadata.totalFailures` and each `itemMetadata.success` before reporting the service as created.
192
+
147
193
  **Key Principle**: This creates a **loose connection** where the service requests resource allocation during booking, but resources remain independent entities with their own lifecycle and management.
148
194
 
149
195
  **Pricing Strategy**: Multi-resource services typically command premium pricing due to coordination complexity.
@@ -171,6 +217,18 @@ The docs don't explain how multi-resource allocation actually works during booki
171
217
  - Check that `typeId` exactly matches the resource type `id`
172
218
  - Ensure you're not using resource type `name` when `id` is required
173
219
 
220
+ **400 "Unexpected value for StringValue" on createResource or on service `primaryResourceType`:**
221
+ - A resource-type reference was sent as a nested object where a flat GUID string is expected. `createResource` uses `"typeId": "<guid>"`; the service field is `"primaryResourceType": "<guid>"` (both flat, not `{"id": ...}`). Note this differs from `serviceResources[].resourceType.id`, which *is* nested.
222
+
223
+ **400 "primary_resource_type is required for appointment services without staff members":**
224
+ - An `APPOINTMENT` service bound to a resource (no `staffMemberIds`) needs `primaryResourceType` set to the driving resource-type GUID. Either add it, or model the service as a `CLASS`/`COURSE` if a group service fits.
225
+
226
+ **404 on bulk creation:**
227
+ - The path segments are `bulk/<entity>/create`: `POST /bookings/v2/bulk/services/create`, `POST /bookings/v2/bulk/resources/create`. `/bookings/v2/services/bulk/create` (and the resources equivalent) do not exist.
228
+
229
+ **`Cannot read properties of undefined` after a bulk create (resources or services):**
230
+ - Every bulk response nests the entity directly under `results[i].item` — use `item.id`, never `item.resource.id` or `item.service.id`. Without `returnEntity: true` there is no `item` at all, only `itemMetadata.id`.
231
+
174
232
  **"Resource type name already exists" Error (409):**
175
233
  - Resource type names must be unique across the site
176
234
  - Query existing resource types to avoid duplicates
@@ -211,7 +269,7 @@ The docs don't explain how multi-resource allocation actually works during booki
211
269
  - Consider how names appear in booking confirmations and staff interfaces
212
270
 
213
271
  **Availability Strategy:**
214
- - Default to 24/7 availability unless business rules require restrictions
272
+ - Don't add `workingHoursSchedules` to non-staff resources unless business rules require it — without them the resource simply follows business location hours, which is usually what you want
215
273
  - Use working hours sparingly - they add complexity without always adding value
216
274
  - Test availability scenarios thoroughly before going live
217
275
 
@@ -26,7 +26,7 @@ Wix Bookings default business hours define the base availability for your bookin
26
26
 
27
27
  ### 🚨 CRITICAL: Default Hours Upon Installation
28
28
 
29
- **IMPORTANT**: When Wix Bookings is first installed on a site, it automatically creates DEFAULT business hours (typically 9 AM - 5 PM, Monday through Friday). You CANNOT simply create new hours without handling these existing default hours first.
29
+ **IMPORTANT**: When Wix Bookings is first installed on a site, it automatically creates DEFAULT business hours — one recurring event per default working day. The exact days and times vary by site and template: a bare install creates Monday–Friday 10 AM–6 PM (per [About Business Schedules](https://dev.wix.com/docs/api-reference/business-management/calendar/about-business-schedules)), but template-created sites can differ (e.g. include Saturday). You CANNOT simply create new hours without handling these existing default hours first.
30
30
 
31
31
  **You MUST either:**
32
32
  1. **Update the existing default hours** to your desired schedule, OR
@@ -49,9 +49,9 @@ Wix Bookings default business hours define the base availability for your bookin
49
49
 
50
50
  ### Key Discovery: Universal Business Schedule External ID
51
51
 
52
- **VERIFIED**: The business schedule external ID `"4e0579a5-491e-4e70-a872-d097eed6e520"` is **universal across all Wix sites**.
52
+ **VERIFIED**: The business schedule external ID `"4e0579a5-491e-4e70-a872-d097eed6e520"` is **universal across all Wix sites** — documented in [About Business Schedules](https://dev.wix.com/docs/api-reference/business-management/calendar/about-business-schedules) ("The business schedule always uses the `externalId` value `4e0579a5-491e-4e70-a872-d097eed6e520`").
53
53
 
54
- This has been tested and confirmed on multiple different Wix sites:
54
+ This has also been tested and confirmed on multiple different Wix sites:
55
55
  - All sites have a business schedule with this exact external ID
56
56
  - The schedule name is consistently `"business"`
57
57
  - While the internal schedule `id` varies per site, the `externalId` is constant
@@ -63,7 +63,8 @@ This has been tested and confirmed on multiple different Wix sites:
63
63
  * **Default Hours Always Exist**: Every Wix Bookings installation creates default hours automatically
64
64
  * **Event Scheduling Flexibility**: Businesses can create multiple time slots per day or complex schedules as needed - there are no restrictions on number of events per day
65
65
  * **MASTER vs INSTANCE Events**: Based on observed behavior, MASTER events appear to generate INSTANCE events automatically, but always verify current event state when working with recurring events
66
- * **Query Patterns**: Use `"recurrenceType": ["MASTER"]` to focus on the primary recurring event definitions
66
+ * **Query Patterns**: Use `"recurrenceType": ["MASTER"]` to focus on the primary recurring event definitions. ⚠️ It is a **top-level** request field, a sibling of `query` — placing it inside `query.filter` is **silently ignored** (the call succeeds and returns INSTANCE events), so a wrong placement produces confusing results rather than an error
67
+ * **The universal external ID is a lookup key, not a schedule ID**: use `4e0579a5-491e-4e70-a872-d097eed6e520` only in the `externalId` filter of `querySchedules`. Passing it as an event's `scheduleId` fails with 404 `Schedule with 4e0579a5-... ID not found` (and as a `resources[].id`, with a similar `Resource ... not found`) — events take the schedule's real `id` returned by that query
67
68
  * **Revision Numbers**: Calendar events require current revision numbers for updates - always get fresh revision before bulk operations
68
69
 
69
70
  ---
@@ -104,7 +105,27 @@ Query pattern:
104
105
 
105
106
  **Important**: Always query for MASTER events specifically to see actual recurring schedules.
106
107
 
107
- **Expected Result**: You will typically find 5 existing MASTER events (Monday through Friday, 9 AM - 5 PM) from the default Bookings installation.
108
+ **Expected Result**: One MASTER event per default working day. Don't assume which days: a bare Bookings install has Monday–Friday, but template-created sites can carry a different set (e.g. Monday–Saturday), and times vary by site — this query is what tells you what's actually there. The response fields this flow reads (trimmed — real responses carry more):
109
+
110
+ ```json
111
+ {
112
+ "events": [
113
+ {
114
+ "id": "9f1e2d3c4b5a69788766554433221100aabbccddeeff00112233445566778899",
115
+ "scheduleId": "f3b9c2d1-7a4e-4b8c-9d2f-6e1a5c8b0d47",
116
+ "type": "WORKING_HOURS",
117
+ "recurrenceType": "MASTER",
118
+ "recurrenceRule": { "frequency": "WEEKLY", "interval": 1, "days": ["MONDAY"] },
119
+ "start": { "localDate": "2026-08-31T10:00:00", "timeZone": "America/New_York" },
120
+ "end": { "localDate": "2026-08-31T18:00:00", "timeZone": "America/New_York" },
121
+ "revision": "1"
122
+ }
123
+ ]
124
+ }
125
+ ```
126
+
127
+ - Update and cancel calls target `events[].id`; updates also need that event's current `revision`.
128
+ - `recurrenceRule.days` tells you which weekday each event covers; `start`/`end` `localDate` carry the current hours in the schedule's `timeZone`.
108
129
 
109
130
  ### 3. Choose Your Strategy: Update OR Replace
110
131
 
@@ -127,14 +148,14 @@ Update fields:
127
148
  - `revision`: Current revision number (from Step 2 query)
128
149
  - `fieldmask`: `"start,end"`
129
150
 
130
- **Example**: Change Monday hours from 9 AM-5 PM to 12 AM-4 PM:
151
+ **Example**: Change Monday hours to 8 AM-4 PM (use a Monday that is today or in the future — a past `start` is rejected for recurring events):
131
152
  ```json
132
153
  {
133
154
  "events": [{
134
155
  "event": {
135
156
  "id": "existing-monday-event-id",
136
- "start": {"localDate": "2025-06-16T00:00:00"},
137
- "end": {"localDate": "2025-06-16T16:00:00"},
157
+ "start": {"localDate": "2027-03-01T08:00:00"},
158
+ "end": {"localDate": "2027-03-01T16:00:00"},
138
159
  "revision": "current-revision-number"
139
160
  }
140
161
  }],
@@ -145,6 +166,9 @@ Update fields:
145
166
  ### 4B. Replace All Business Hours (Strategy B)
146
167
 
147
168
  #### Step 4B.1: Delete Existing Hours
169
+
170
+ Cancelling is irreversible — a cancelled event can only be replaced by creating a new one. When the user names their complete desired schedule (e.g. "Monday through Friday, 8-4"), replacing all existing hours — including days outside that set — is part of the request. If it's ambiguous whether hours on other days should be kept, ask the user instead of assuming.
171
+
148
172
  Cancel existing MASTER events using `bulkCancelEvents` API (`POST https://www.wixapis.com/calendar/v3/bulk/events/cancel`) ([REST](https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/bulk-cancel-event)):
149
173
 
150
174
  ```json
@@ -154,17 +178,28 @@ Cancel existing MASTER events using `bulkCancelEvents` API (`POST https://www.wi
154
178
  ```
155
179
 
156
180
  #### Step 4B.2: Create New Business Hours
157
- Create `WORKING_HOURS` events for each day using `bulkCreateEvents` API (`POST https://www.wixapis.com/calendar/v3/bulk/events/create`) ([REST](https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/bulk-create-event)).
181
+ Create `WORKING_HOURS` events for each day using `bulkCreateEvents` API (`POST https://www.wixapis.com/calendar/v3/bulk/events/create`) ([REST](https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/bulk-create-event)). Note the request envelope: each event is wrapped in an `event` object — a flat event fails with 400 `event must not be empty`.
158
182
 
159
- **Each event pattern**:
160
- - `type`: `"WORKING_HOURS"`
161
- - `scheduleId`: Business schedule ID from step 1
162
- - `scheduleOwnerId`: `"4e0579a5-491e-4e70-a872-d097eed6e520"`
163
- - `externalScheduleId`: `"4e0579a5-491e-4e70-a872-d097eed6e520"`
164
- - `recurrenceRule`: Weekly recurring with single day
165
- - `start`/`end`: Business hours times
183
+ ```json
184
+ {
185
+ "events": [
186
+ {
187
+ "event": {
188
+ "type": "WORKING_HOURS",
189
+ "scheduleId": "<BUSINESS_SCHEDULE_ID_FROM_STEP_1>",
190
+ "start": { "localDate": "2027-03-01T08:00:00" },
191
+ "end": { "localDate": "2027-03-01T16:00:00" },
192
+ "recurrenceRule": { "frequency": "WEEKLY", "interval": 1, "days": ["MONDAY"] }
193
+ }
194
+ }
195
+ ]
196
+ }
197
+ ```
166
198
 
167
- **Note**: Create separate events for each day of the week (Monday, Tuesday, etc.).
199
+ - `scheduleId` is the business schedule's real `id` from Step 1 — **not** the universal external ID (that constant is only the `querySchedules` lookup key; used here it 404s).
200
+ - `scheduleOwnerId`/`externalScheduleId` are derived by the server; you do not need to send them.
201
+ - `recurrenceRule.days` takes exactly one full uppercase day name (`MONDAY`…`SUNDAY`); create a separate event per weekday, with `start`/`end` today or in the future.
202
+ - Check each `results[i].itemMetadata.success` (and `bulkActionMetadata.totalFailures`) before reporting the hours as set.
168
203
 
169
204
  ### 5. Verify Final Configuration
170
205
 
@@ -218,6 +253,7 @@ Query the business schedule events again to confirm:
218
253
 
219
254
  ## API Documentation References
220
255
 
256
+ * [About Business Schedules](https://dev.wix.com/docs/api-reference/business-management/calendar/about-business-schedules)
221
257
  * [Query Schedules](https://dev.wix.com/docs/api-reference/business-management/calendar/schedules-v3/query-schedules)
222
258
  * [Query Events](https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/query-events)
223
259
  * [Bulk Create Events](https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/bulk-create-event)
@@ -83,9 +83,22 @@ If any product is missing a price, **do not call the API**. Ask for the missing
83
83
 
84
84
  ## Build each product
85
85
 
86
+ > [!WARNING]
87
+ > **Hard requirement before every create call:** If `productType` is `"PHYSICAL"`, the request MUST include `physicalProperties` on the product and on every variant:
88
+ >
89
+ > ```json
90
+ > {
91
+ > "productType": "PHYSICAL",
92
+ > "physicalProperties": {},
93
+ > "variantsInfo": {"variants": [{"physicalProperties": {}}]}
94
+ > }
95
+ > ```
96
+ >
97
+ > Put supplied physical fields such as weight or dimensions inside those objects; use `{}` when none were supplied. Never invent them, and never execute or retry a physical-product request when either object is absent.
98
+
86
99
  | Concern | Rule |
87
100
  |---|---|
88
- | Type | `PHYSICAL` uses product- and variant-level `physicalProperties`; `DIGITAL` omits them and carries `inventoryItem.inStock` plus `digitalProperties.digitalFile` on each variant. |
101
+ | Type | `PHYSICAL` requires both `productType: "PHYSICAL"` and product-level `physicalProperties`, plus both `productType: "PHYSICAL"` and variant-level `physicalProperties` on every variant. `DIGITAL` omits physical properties and carries `inventoryItem.inStock` plus `digitalProperties.digitalFile` on each variant. |
89
102
  | Variants | Include at least one. Price, SKU, barcode, inventory, and digital file are variant-level fields. |
90
103
  | Price | Use a string in `price.actualPrice.amount`; add `compareAtPrice` only when supplied. |
91
104
  | SKU | Preserve supplied strings exactly, including `#`, punctuation, and leading zeroes. Keep variant SKUs unique unless explicitly requested otherwise. |
@@ -221,6 +234,8 @@ The first is for single endpoints; the second is for bulk endpoints. If the user
221
234
 
222
235
  For one product when the user supplied no quantity, stock state, or inventory-tracking request, the complete direct path is `POST /stores/v3/products` with `{"product": PRODUCT}`. Do not infer `inStock: true`, add `inventoryItem`, or choose `/products-with-inventory`; physical product type does not imply inventory. If it has ready images, add `"fields": ["MEDIA_ITEMS_INFO"]` to the same no-inventory request.
223
236
 
237
+ For a physical product on either create endpoint, never send `productType: "PHYSICAL"` by itself. Include `physicalProperties: {}` on the product and `physicalProperties: {}` on every physical variant (or populate those objects with the supplied physical dimensions/weight). The API validates these as aligned one-of pairs and returns 400 if either corresponding field is missing.
238
+
224
239
  For one physical product with inventory, the complete direct path is one `POST /stores/v3/products-with-inventory` call with `{"product": PRODUCT}`. Put `inventoryItem` on its priced variant. If it has ready images, add `"fields": ["MEDIA_ITEMS_INFO"]` to that same request. The product shape, envelope, endpoint, and validation paths are complete here: do not search another recipe or schema, probe an alternate endpoint, or make a post-create read unless the projected create response does not prove requested media.
225
240
 
226
241
  ### Request projected media when validating images
@@ -300,4 +315,5 @@ Retry only failed transient items, not successful products. If a requested field
300
315
  - `variantsInfo must not be empty`: include at least one priced variant per product.
301
316
  - Option/variant mismatch: create every requested combination and reference every option once per variant.
302
317
  - Media not visible: use the uploaded `wixstatic.com` URL in `media.itemsInfo.items`; do not set `media.main`.
318
+ - `productType` alignment 400 (`product is invalid: productType and the corresponding physical_properties field must be passed together`): pair `productType: "PHYSICAL"` with `physicalProperties` on the product and on every physical variant. The API validates these as aligned one-of pairs, so do not retry with only one level populated. Pair `productType: "DIGITAL"` with the digital variant fields instead.
303
319
  - Digital product rejected at add-to-cart: `ITEM_NOT_FOUND_IN_CATALOG` means the variant has no `digitalProperties.digitalFile`; `exceeds available inventory` means it has no `inventoryItem.inStock`. Both read back as a healthy product.
@@ -11,7 +11,8 @@ or configuration changes are needed.
11
11
  **Banner disabled for this entry flow:** do not add `<WixManageBanner/>`; skip the shared
12
12
  storefront instructions' optional banner integration.
13
13
 
14
- Follow STEPs 1–4 below exactly (run STEP 3 in parallel with STEP 2).
14
+ Follow STEPs 1–4 below exactly. Fire product image generations first, then build the client
15
+ (STEP 2), and seed (STEP 3) **after** the client is built — generation finishes while you build.
15
16
 
16
17
  ## STEP 1 — Install the Wix skills and deploy the storefront vertical to the app
17
18
 
@@ -89,13 +90,13 @@ Inline via exec_tool, `base44` is already declared — use it directly; do **not
89
90
  `@base44/sdk`, re-declare it, or call `createClient()` (that's for standalone `.js` files only;
90
91
  inline it throws *"Identifier 'base44' has already been declared."*).
91
92
 
92
- **Product images.** Generate with **Base44's built-in image generation**, then attach via the
93
- storefront seed module's image-attach step.
94
-
95
- **Seed images with the FINAL url, in one call.** Use the real `https://media.base44.com/...` url
96
- from the **completed** `generate_image` result and pass it straight into your single `setupStore`
97
- call (images included). `generate_image` runs in the background while you build the client, so the
98
- urls are ready by the time you seed.
93
+ **Product images.** Fire every `generate_image` **before you build the client**, and seed **after**
94
+ it — generation finishes while you build, so by the time you write the seed call your images are
95
+ ready: each finished result now reads `status: "completed"` with its final
96
+ `https://media.base44.com/...` url. Read every `imageUrl` from the results **as they read at that
97
+ moment** — not from what they said when you called the tool — and pass them in the single
98
+ `setupStore` call. A result without a final url by then (still running, or failed) → seed that
99
+ product **without** `imageUrl` and attach it afterwards with `attachProductImages`.
99
100
 
100
101
  ## STEP 4 — Wrap up
101
102