@wix/agent-skills 1.31.1 → 1.31.2

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.
Files changed (90) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/STORES_VERSIONING.md +1 -1
  3. package/skills/wix-headless-kit/SKILL.md +16 -7
  4. package/skills/wix-headless-kit/guides/api-run.md +81 -51
  5. package/skills/wix-headless-kit/guides/existing-site.md +6 -0
  6. package/skills/wix-headless-kit/install/AGENTS.md +3 -2
  7. package/skills/wix-headless-kit/install/attach.mjs +32 -27
  8. package/skills/wix-headless-kit/install/background.mjs +32 -0
  9. package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
  10. package/skills/wix-headless-kit/install/check.mjs +2 -2
  11. package/skills/wix-headless-kit/install/context.mjs +49 -5
  12. package/skills/wix-headless-kit/install/deploy.mjs +33 -8
  13. package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
  14. package/skills/wix-headless-kit/install/install.mjs +95 -0
  15. package/skills/wix-headless-kit/install/pins.json +1 -1
  16. package/skills/wix-headless-kit/install/proc.mjs +46 -0
  17. package/skills/wix-headless-kit/install/setup.mjs +16 -25
  18. package/skills/wix-headless-kit/install/templates.mjs +2 -2
  19. package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
  20. package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
  21. package/skills/wix-headless-templates/blog/project/package.json +1 -1
  22. package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
  23. package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
  24. package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
  25. package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
  26. package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
  27. package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
  28. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -1
  29. package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
  30. package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
  31. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
  32. package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
  33. package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
  34. package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
  35. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  36. package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
  37. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +2 -1
  38. package/skills/wix-headless-templates/events/rest/events.ts +4 -4
  39. package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
  40. package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
  41. package/skills/wix-headless-templates/events/seed/seed-events.mjs +2 -1
  42. package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
  43. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
  44. package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
  45. package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
  46. package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
  47. package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
  48. package/skills/wix-headless-templates/members/rest/members.ts +4 -4
  49. package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
  50. package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
  51. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +2 -1
  52. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
  53. package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
  54. package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
  55. package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
  56. package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
  57. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
  58. package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
  59. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
  60. package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
  61. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +2 -1
  62. package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
  63. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
  64. package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
  65. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
  66. package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
  67. package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
  68. package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
  69. package/skills/wix-headless-templates/storefront/seed/SEED.md +27 -9
  70. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +2 -1
  71. package/skills/wix-manage/SKILL.md +3 -3
  72. package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
  73. package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
  74. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
  75. package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
  76. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
  77. package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
  78. package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
  79. package/skills/wix-manage/references/forms/update-form.md +3 -0
  80. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
  81. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
  82. package/skills/wix-manage/references/sites/create-headless-site.md +7 -50
  83. package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
  84. package/skills/wix-manage/references/sites/read-site-context.md +4 -0
  85. package/skills/wix-manage/references/sites/upload-static-site.md +67 -31
  86. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
  87. package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
  88. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
  89. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
  90. package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "Update Product with Options (Catalog V3)"
3
- description: Modifies existing products and variants using Catalog V3 Products API. Covers adding/removing option choices, variant-specific pricing, product visibility (hide, unhide, or show a product in the storefront — a product-level `visible` update, never a delete), and revision-based updates to prevent conflicts.
3
+ description: "Start here for existing Catalog V3 product updates, including digital products that fail at add-to-cart with ITEM_NOT_FOUND_IN_CATALOG. Includes name lookup, Get Product, and PATCH in one flow, plus importing a supplied PDF and attaching it to the existing variant. Covers options, variant prices and SKUs, product visibility (hide/unhide through `visible`, never delete), description edits that preserve Rich Content, and revision-based updates. Read another recipe only for a field or operation this flow does not cover."
4
4
  ---
5
5
  **RECIPE**: Business Recipe - Updating a Wix Store Product (Catalog V3)
6
6
 
@@ -14,7 +14,8 @@ Every Catalog V3 product update is revision-based:
14
14
  - Use [Get Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product) to retrieve the current product, its `product.revision`, and its existing variants. Search Products and Query Products responses do not include `variantsInfo.variants`, so a variant or price update assembled from a search result sends an empty variants array and is rejected. Re-read the product before every variant-level update.
15
15
  - Include `product.id` and the current `product.revision` in every [Update Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/update-product) PATCH body.
16
16
  - Update Product is a partial update: only `product`, `product.id`, and `product.revision` are required, and top-level fields you omit (for example `name`, `ribbon`, `brand`) are left unchanged. The full-array overwrite rule applies only to the repeated fields `options`, `modifiers`, and `variantsInfo.variants`.
17
- - For simple text/HTML description updates, prefer `plainDescription`. Use `description` only when sending a Rich Content object.
17
+ - Get Product returns a default set of fields. When an update keeps or carries over existing projected data, request that data in `fields` before building the PATCH. For any edit that keeps part of the existing product description, request `DESCRIPTION`; a read without it has no Rich Content document to preserve.
18
+ - For description changes, see [Update Description](#update-description): replacing the whole description uses `plainDescription`; editing any existing content uses the Rich Content `description` field.
18
19
 
19
20
  ### Find the product by name
20
21
 
@@ -24,12 +25,16 @@ curl -X POST "https://www.wixapis.com/stores/v3/products/search" \
24
25
  -H "Authorization: <AUTH>" \
25
26
  -d '{
26
27
  "search": {
27
- "expression": "Product name"
28
+ "search": {
29
+ "expression": "Product name",
30
+ "fields": ["name"],
31
+ "fuzzy": false
32
+ }
28
33
  }
29
34
  }'
30
35
  ```
31
36
 
32
- For product-name lookup, prefer Search Products before retrieving the product by ID. Search only resolves the product ID; it does not replace the Get Product call.
37
+ Read `products` from the search response. Prefer an exact name match; otherwise select a clear, unambiguous match, such as a name differing only in capitalization. If no result clearly identifies the requested product, or multiple products could match, ask the user which product they mean. Use the selected `products[].id` in Get Product; search only resolves the ID.
33
38
 
34
39
  ### Get the current revision
35
40
 
@@ -38,6 +43,15 @@ curl -X GET "https://www.wixapis.com/stores/v3/products/{productId}" \
38
43
  -H "Authorization: <AUTH>"
39
44
  ```
40
45
 
46
+ To edit an existing description, request its Rich Content document in the same read:
47
+
48
+ ```bash
49
+ curl -X GET "https://www.wixapis.com/stores/v3/products/{productId}?fields=DESCRIPTION" \
50
+ -H "Authorization: <AUTH>"
51
+ ```
52
+
53
+ `product.description` is returned only when `DESCRIPTION` is requested. Request every other projected field that you need to carry over with its documented `fields` value as well.
54
+
41
55
  ## Common Update Patterns
42
56
 
43
57
  ### Hide or Show a Product
@@ -66,9 +80,16 @@ Visibility behaviour to report back accurately:
66
80
  - For a product **with** options, product and variant visibility are independent: setting `product.visible` to `false` leaves each `variantsInfo.variants[].visible` as it was.
67
81
  - Point-of-sale visibility is a separate field, `visibleInPos`. Only change it when the user asks about POS. It is always `false` for `productType: DIGITAL`.
68
82
 
69
- ### Update Description Only
83
+ ### Update Description
84
+
85
+ Choose the flow from the user's request:
86
+
87
+ - "Set the description to X", "replace the description", or "write a new description": **Replace the Description**.
88
+ - "Add", "append", "prepend", "insert", "fix a typo", or "remove" existing text: **Edit the Existing Description**. Any request that keeps part of the current description is an edit.
89
+
90
+ #### Replace the Description
70
91
 
71
- For a normal user request like "set the product description to X", use `plainDescription` with valid HTML. The API converts it to rich content.
92
+ For a request like "set the product description to X", use `plainDescription` with valid HTML. The API converts it to rich content.
72
93
 
73
94
  Do not send a plain string in `description`. `description` is a Rich Content object.
74
95
 
@@ -85,7 +106,7 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
85
106
  }'
86
107
  ```
87
108
 
88
- Use `description` only when you intentionally need to send Rich Content:
109
+ For a full replacement with node types beyond the paragraph below, read [Author Ricos Rich Content](../rich-content/author-ricos-rich-content.md). Existing-document edits use the read-modify-write flow below:
89
110
 
90
111
  ```bash
91
112
  curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
@@ -123,6 +144,77 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
123
144
  }'
124
145
  ```
125
146
 
147
+ Both fields replace the whole description. To keep any existing content, use **Edit the Existing Description**.
148
+
149
+ #### Edit the Existing Description
150
+
151
+ For prepend, append, and text removal, execute the documented calls directly: the examples in this recipe and the request/response contract below provide the fields this flow needs. Reuse this contract rather than rediscovering the Search, Get, and Update schemas. Consult further API documentation when the request requires a field or node type not covered here, or when a documented call fails. Read [Author Ricos Rich Content](../rich-content/author-ricos-rich-content.md) when creating other node types.
152
+
153
+ | Call | Request | Response fields used next |
154
+ |---|---|---|
155
+ | Search Products, when given a name | Use [Find the product by name](#find-the-product-by-name); `search.search` contains `expression`, `fields: ["name"]`, and `fuzzy: false`. | `products[]` contains `id` and `name`; select the product using the name-matching guidance above. |
156
+ | Get Product | `GET https://www.wixapis.com/stores/v3/products/{productId}?fields=DESCRIPTION` | `product.id`, `product.revision`, and the full `product.description`. |
157
+ | Update Product | `PATCH https://www.wixapis.com/stores/v3/products/{productId}` with `{ product: { id, revision, description }, fields: ["DESCRIPTION"] }`, using the read ID/revision and complete edited document. | `product.description` verifies the edit; `product.revision` is the new revision. |
158
+
159
+ The request-level `fields` array projects response data; it is separate from `search.search.fields`, which selects the fields searched. A description-only PATCH needs only the three product fields shown above; variant, option, price, and physical-property fields belong to other update flows.
160
+
161
+ Use a single read-modify-write operation: locate the product, read its description and revision, make the requested change in memory, then PATCH once and verify the response.
162
+
163
+ 1. Read [Get Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product) with `?fields=DESCRIPTION`. Its response wraps the document as `product.description` and the current revision as `product.revision`. If the description is missing or has no `nodes`, stop and ask before replacing it.
164
+ 2. Copy the complete returned `product.description`, including node IDs, decorations, `metadata`, and `documentStyle`, **before locating any nodes to edit**. Traverse the copy and keep references to its nodes; edit that same copy and send it in the PATCH. For prepend/append, insert one new `PARAGRAPH` at the beginning/end of its `nodes`. For a wording change, modify only the matching `TEXT` node's `textData.text`.
165
+ 3. For removal, trim only the requested substring from the matching TEXT run and keep its decorations. When the whole run or node is targeted, remove that entry from its parent `nodes` array. If text spans runs, trim the matching portion of each run. Preserve all other nodes, spacer paragraphs, and document properties. If the requested change is already present, report that state and finish without a write.
166
+ 4. Check the in-memory PATCH document before sending: the targeted text has the requested value and untouched content matches the read document. Then send [Update Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/update-product) to `PATCH https://www.wixapis.com/stores/v3/products/{productId}` with `product.id`, the read revision, the full edited `product.description`, and top-level `fields: ["DESCRIPTION"]`. Leave other product fields out of this description-only update; `plainDescription` is for whole-description replacements.
167
+ 5. Verify `product.description` in the PATCH response: the requested change is present and untouched nodes, decorations, and document properties remain. Confirm completion from that response and finish. A successful PATCH has already performed the edit; verification does not require another PATCH. If the response is incomplete, read once with `?fields=DESCRIPTION` to verify. If a revision conflict rejects the write, read the latest document and reapply the requested change only if it is still needed.
168
+
169
+ For a prepend, build the complete PATCH body from the Get Product response (`getResponse` below). For an append, use `push` instead of `unshift`:
170
+
171
+ ```javascript
172
+ const product = getResponse.product;
173
+ const description = structuredClone(product.description);
174
+ description.nodes.unshift({
175
+ type: "PARAGRAPH",
176
+ nodes: [{
177
+ type: "TEXT",
178
+ textData: { text: "New text.", decorations: [] }
179
+ }],
180
+ paragraphData: { textStyle: { textAlignment: "AUTO" } }
181
+ });
182
+ const patchBody = {
183
+ product: { id: product.id, revision: product.revision, description },
184
+ fields: ["DESCRIPTION"]
185
+ };
186
+ ```
187
+
188
+ Send `patchBody` as the JSON body of the PATCH above. The copy retains the returned document's other properties and existing nodes, including links and buttons; the response's `product.description` includes the edited document because the request projects `DESCRIPTION`.
189
+
190
+ For example, remove only `read ` from a TEXT run containing `read the guide`, retaining its BOLD and LINK decorations and siblings. Locate the run in the cloned document, including nested nodes, rather than assuming a paragraph/run index. This example handles a phrase within one TEXT run; a phrase spanning runs uses the per-run trimming in step 3, with all run references taken from the copy.
191
+
192
+ ```javascript
193
+ const product = getResponse.product;
194
+ const description = structuredClone(product.description);
195
+ const target = "read the guide";
196
+ const replacement = "the guide";
197
+ const matches = [];
198
+ function locate(node) {
199
+ if (node.type === "TEXT" && node.textData.text.includes(target)) matches.push(node.textData);
200
+ for (const child of node.nodes ?? []) locate(child);
201
+ }
202
+ locate(description);
203
+ if (matches.length !== 1) throw new Error("Clarify which text occurrence to edit");
204
+ const textData = matches[0];
205
+ const originalText = textData.text;
206
+ const start = originalText.indexOf(target);
207
+ if (start !== originalText.lastIndexOf(target)) throw new Error("Clarify which text occurrence to edit");
208
+ textData.text = originalText.slice(0, start) + replacement + originalText.slice(start + target.length);
209
+ if (textData.text === originalText || textData.text.includes(target)) {
210
+ throw new Error("The PATCH document must contain the requested edit before writing");
211
+ }
212
+ const patchBody = {
213
+ product: { id: product.id, revision: product.revision, description },
214
+ fields: ["DESCRIPTION"]
215
+ };
216
+ ```
217
+
126
218
  ### Update Options and Variants
127
219
 
128
220
  When adding or changing options and variants, send the full option definitions and one variant for each option-choice combination. Use `optionChoiceNames` to reference choices.
@@ -339,8 +431,11 @@ its `id`, its `choices` (by `optionChoiceIds` — or `optionChoiceNames`, see *G
339
431
 
340
432
  ```bash
341
433
  # A choice image + a variant's sale price, one PATCH. Both arrays are complete and keep their ids —
342
- # this exact shape is the one that succeeds; the two shortcuts (choices by name only, or a variant
343
- # without its choices/price) each 400.
434
+
435
+ > These calls need the Wix Stores app on the site (`appDefId` `215238eb-22a5-4c36-9e7b-e7c08025e04e`); a `428`, a `400` naming the app, or an empty result on a site that should have content means it is not there. [List Installed Apps](../app-installation/list-installed-apps.md) shows what the site has; [Install Wix Apps](../app-installation/install-wix-apps.md) adds it by that id.
436
+
437
+ # keep the option and choice IDs when rebuilding the options array, and each variant
438
+ # with its choices and price. Omitting those identities or required variant fields can reject the update.
344
439
  curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
345
440
  -H "Content-Type: application/json" -H "Authorization: <AUTH>" \
346
441
  -d '{ "product": { "id": "{productId}", "revision": "{currentRevision}",
@@ -357,9 +452,67 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
357
452
  "choices": [ { "optionChoiceIds": { "optionId": "{optionId}", "choiceId": "{choiceId2}" } } ] } ] } } }'
358
453
  ```
359
454
 
455
+ ### Prepare a Price or SKU Update
456
+
457
+ For an existing product's price or SKU, use the name lookup above, then Get Product. Request `?fields=VARIANT_OPTION_CHOICE_NAMES&fields=MERCHANT_DATA&fields=PRODUCT_CHOICES_MEDIA_REFERENCES&fields=PRODUCT_CHOICES_DISPLAY_IMAGE` so the same read supplies variant choice names, permitted cost data, and choice media to preserve. The response is `{ product: { id, revision, options, variantsInfo: { variants } } }`; search results cannot supply the variants.
458
+
459
+ Build the complete writable options and variants from that response, then change only the requested field. **Every variants PATCH also includes the complete `options` array**, even for an SKU-only change; for a product without options, send `options: []`. Omitting options caused `428 MISSING_OPTIONS_ON_UPDATE_VARIANTS`. A variant retains its `id`, `choices`, price, and other writable fields. The example below copies the writable fields used by these flows and strips calculated monetary values; inventory status and variant media are read-only and are not part of this update.
460
+
461
+ ```javascript
462
+ const product = getResponse.product;
463
+ const pick = (source, keys) => Object.fromEntries(keys
464
+ .filter(key => source[key] !== undefined)
465
+ .map(key => [key, structuredClone(source[key])]));
466
+ const amountOnly = value => ({ amount: value.amount });
467
+ const options = (product.options ?? []).map(option => ({
468
+ ...pick(option, ["id", "name", "optionRenderType"]),
469
+ choicesSettings: { choices: option.choicesSettings.choices.map(choice =>
470
+ pick(choice, ["choiceId", "name", "choiceType", "colorCode", "media", "displayImage"])) }
471
+ }));
472
+ const variants = product.variantsInfo.variants.map(variant => {
473
+ const copy = pick(variant, ["id", "visible", "sku", "barcode", "physicalProperties"]);
474
+ copy.choices = (variant.choices ?? []).map(choice => ({
475
+ optionChoiceNames: pick(choice.optionChoiceNames, ["optionName", "choiceName", "renderType"])
476
+ }));
477
+ copy.price = { actualPrice: amountOnly(variant.price.actualPrice) };
478
+ if (variant.price.compareAtPrice) copy.price.compareAtPrice = amountOnly(variant.price.compareAtPrice);
479
+ if (variant.revenueDetails?.cost) copy.revenueDetails = { cost: amountOnly(variant.revenueDetails.cost) };
480
+ if (variant.digitalProperties) {
481
+ copy.digitalProperties = variant.digitalProperties.digitalFile
482
+ ? { digitalFile: { id: variant.digitalProperties.digitalFile.id } } : {};
483
+ }
484
+ if (copy.physicalProperties?.pricePerUnit) delete copy.physicalProperties.pricePerUnit.value;
485
+ return copy;
486
+ });
487
+ const variantPatchProduct = {
488
+ id: product.id, revision: product.revision, options,
489
+ variantsInfo: { variants }
490
+ };
491
+ ```
492
+
493
+ For the single-variant product case (a user asking to change a simple product's price), use the prepared body above:
494
+
495
+ ```javascript
496
+ if (variants.length !== 1) throw new Error("Clarify which variants should receive the price change");
497
+ variants[0].price.actualPrice.amount = "25"; // requested price
498
+ const pricePatchBody = { product: variantPatchProduct };
499
+ ```
500
+
501
+ For a named option choice such as `Size = Large`, use its returned choice names to select one variant, then change its SKU:
502
+
503
+ ```javascript
504
+ const matches = variants.filter(variant => variant.choices.some(choice =>
505
+ choice.optionChoiceNames.optionName === "Size" && choice.optionChoiceNames.choiceName === "Large"));
506
+ if (matches.length !== 1) throw new Error("Clarify which variant should receive the SKU change");
507
+ matches[0].sku = "MUG-L-001"; // requested SKU
508
+ const skuPatchBody = { product: variantPatchProduct };
509
+ ```
510
+
511
+ Send the selected body to `PATCH https://www.wixapis.com/stores/v3/products/{productId}` in the same ExecuteWixAPI invocation as the name lookup and Get Product. Its response is `{ product: { id, revision, variantsInfo: { variants } } }`; verify the changed variant by its retained ID and confirm from that response. These examples provide the request fields and response paths for price and SKU changes; proceed directly after reading this recipe. Discover further schemas only for fields not covered here or a documented call that fails.
512
+
360
513
  ### Update Variant Price Only
361
514
 
362
- Read `{existingVariantId}` off the Get Product response; a Search or Query Products result does not carry it.
515
+ Read `{existingVariantId}` off the Get Product response; a Search or Query Products result does not carry it. The curl below is for a single-variant product without options. For an optioned product, use the complete options/variants body in **Prepare a Price or SKU Update**.
363
516
 
364
517
  ```bash
365
518
  curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
@@ -369,10 +522,12 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
369
522
  "product": {
370
523
  "id": "{productId}",
371
524
  "revision": "{currentRevision}",
525
+ "options": [],
372
526
  "variantsInfo": {
373
527
  "variants": [
374
528
  {
375
529
  "id": "{existingVariantId}",
530
+ "choices": [],
376
531
  "price": {
377
532
  "actualPrice": {
378
533
  "amount": "29.99"
@@ -387,7 +542,23 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
387
542
 
388
543
  ### Attach a Digital File
389
544
 
390
- A `DIGITAL` product is **sellable** only when its variant carries both a digital file and stock. Upload the file first ([Upload Media to Wix](../media/upload-media-to-wix.md) → Generate Upload URL, then `PUT` the bytes), then send its `file.id` on the variant — `digitalProperties` is a variant field, never a product field.
545
+ For an existing digital product rejected at add-to-cart with `ITEM_NOT_FOUND_IN_CATALOG`, use this repair flow. For a supplied external PDF URL, the table below is the complete request/response contract; execute it directly after reading this recipe. Use its linked method URLs as source citations. Further schema discovery is needed only for a field not covered here or a documented call that fails.
546
+
547
+ First locate the existing product with the name lookup above, then Get Product for its revision and variants. If the requested product is absent or ambiguous, ask for the correct product or site and stop before uploading media. Repairing an existing product does not authorize creating a replacement.
548
+
549
+ A `DIGITAL` product is sellable only when its variant has both a digital file and stock. For a product already in stock, keep that stock unchanged and attach only the missing file. `digitalProperties` belongs to the variant.
550
+
551
+ | Call | Request | Response fields used next |
552
+ |---|---|---|
553
+ | [Search Products](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products) | Use the `search.search` name lookup above. | `products[].id` and `name` select the existing product. |
554
+ | [Get Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product) | `GET https://www.wixapis.com/stores/v3/products/{productId}?fields=VARIANT_OPTION_CHOICE_NAMES&fields=MERCHANT_DATA&fields=PRODUCT_CHOICES_MEDIA_REFERENCES&fields=PRODUCT_CHOICES_DISPLAY_IMAGE` | `product.id`, `revision`, `productType`, `options`, and `variantsInfo.variants` supply the complete writable state. |
555
+ | [Import File](https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/import-file) | `POST https://www.wixapis.com/site-media/v1/files/import` with `{ "url": "{suppliedPdfUrl}", "mimeType": "application/pdf", "displayName": "download.pdf", "private": true }` | `file.id` and `file.operationStatus`. Import the exact supplied URL once; retain this ID. |
556
+ | [Get File Descriptor](https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/get-file-descriptor), only while PENDING | `GET https://www.wixapis.com/site-media/v1/files/get-file-by-id?fileId={fileId}` | `file.operationStatus`: wait for `READY`, stop on `FAILED`. The response is wrapped in `file`. |
557
+ | [Update Product](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/update-product) | `PATCH https://www.wixapis.com/stores/v3/products/{productId}` with `{ product: { id, revision, options, variantsInfo: { variants } } }` | `product.variantsInfo.variants[].digitalProperties.digitalFile.id` confirms the attachment on the retained variant ID. |
558
+
559
+ Chain these requests in one ExecuteWixAPI invocation; with `wix.request`, the REST response body is in `response.data`. Private import keeps the stored download private. For a local file rather than a supplied URL, read [Upload Media to Wix](../media/upload-media-to-wix.md) and use its Generate Upload URL flow instead.
560
+
561
+ Prepare the complete options/variants body as in **Prepare a Price or SKU Update**, retain every variant ID, choice and price, and set the requested variant's `digitalProperties.digitalFile.id` to the imported file ID. For one existing variant with no options:
391
562
 
392
563
  ```bash
393
564
  curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
@@ -397,22 +568,21 @@ curl -X PATCH "https://www.wixapis.com/stores/v3/products/{productId}" \
397
568
  "product": {
398
569
  "id": "{productId}",
399
570
  "revision": "{currentRevision}",
571
+ "options": [],
400
572
  "variantsInfo": {
401
- "variants": [
402
- {
403
- "id": "{existingVariantId}",
404
- "price": { "actualPrice": { "amount": "9.99" } },
405
- "visible": true,
406
- "inventoryItem": { "inStock": true },
407
- "digitalProperties": { "digitalFile": { "id": "{fileId}" } }
408
- }
409
- ]
573
+ "variants": [{
574
+ "id": "{existingVariantId}",
575
+ "choices": [],
576
+ "price": { "actualPrice": { "amount": "{existingPrice}" } },
577
+ "visible": true,
578
+ "digitalProperties": { "digitalFile": { "id": "{fileId}" } }
579
+ }]
410
580
  }
411
581
  }
412
582
  }'
413
583
  ```
414
584
 
415
- Confirm from `product.variantsInfo.variants[].digitalProperties.digitalFile` in the response.
585
+ Copy any other writable variant fields from the read product using the preparation example. Chain the product lookup, read, selected upload path, and one PATCH in one ExecuteWixAPI invocation. Confirm from `product.variantsInfo.variants[].digitalProperties.digitalFile.id` in the PATCH response; no extra product read is needed when that response supplies it.
416
586
 
417
587
  ## Important Notes
418
588
 
@@ -439,4 +609,4 @@ Confirm from `product.variantsInfo.variants[].digitalProperties.digitalFile` in
439
609
  | `Missing option choices` or `INVALID_DEFAULT_VARIANT` | Product has options but at least one variant has no matching choices | Rebuild `variantsInfo.variants` so every variant includes choices for all product options |
440
610
  | `DIGITAL_PRODUCT_CANNOT_BE_VISIBLE_IN_POS` | Sent `visibleInPos: true` on a digital product | Digital products can't be visible in POS; leave `visibleInPos` out of the body |
441
611
  | `ITEM_NOT_FOUND_IN_CATALOG` at add-to-cart, product exists | A `DIGITAL` variant has no `digitalProperties.digitalFile` | Attach a file — see [Attach a Digital File](#attach-a-digital-file) |
442
- | `exceeds available inventory` at add-to-cart, product exists | The variant has no stock (`DIGITAL` products included) | Set `inventoryItem.inStock: true` on the variant |
612
+ | `exceeds available inventory` at add-to-cart, product exists | The variant has no stock (`DIGITAL` products included) | Update its stock through the Inventory API; inventory is separate from an Update Product PATCH |