@wix/agent-skills 1.19.0 → 1.19.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.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +33 -31
- package/skills/wix-app/references/DASHBOARD_PAGE.md +6 -10
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +17 -8
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +53 -131
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +1 -2
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +88 -73
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +46 -22
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +20 -6
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +9 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +71 -31
- package/skills/wix-app/references/editor-react-component/PARTS.md +5 -2
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +4 -5
- package/skills/wix-app/scripts/pkg-root.cjs +73 -0
- package/skills/wix-app/scripts/scan-a11y-code.cjs +310 -40
- package/skills/wix-app/scripts/scan-a11y-eslint.cjs +118 -24
- package/skills/wix-app/scripts/scan-a11y-render.cjs +551 -0
- package/skills/wix-app/scripts/scan-a11y-review.cjs +373 -0
- package/skills/wix-base44-connector/SKILL.md +67 -43
- package/skills/wix-base44-connector/scripts/utils.cjs +39 -3
- package/skills/wix-docs/SKILL.md +47 -20
- package/skills/wix-headless/SKILL.md +1 -0
- package/skills/wix-headless/entry/bootstrap.mjs +106 -50
- package/skills/wix-headless/entry/skill.md +5 -3
- package/skills/wix-headless/references/CAPABILITIES.md +7 -2
- package/skills/wix-headless/references/SDK_HANDOFF.md +2 -1
- package/skills/wix-headless/references/inline-recipes/experience-store.md +48 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +350 -114
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +1 -1
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +25 -5
- package/skills/wix-headless/references/managed/CREATE.md +5 -0
- package/skills/wix-headless-fast/SKILL.md +25 -7
- package/skills/wix-headless-fast/entry/skill.md +23 -14
- package/skills/wix-headless-fast/install/fast-path.mjs +48 -5
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +57 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +3 -3
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +204 -35
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +146 -36
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +8 -2
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +95 -20
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +21 -2
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +104 -18
- package/skills/wix-manage/SKILL.md +16 -10
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +4 -1
- package/skills/wix-manage/references/bookings/create-appointment-service.md +3 -1
- package/skills/wix-manage/references/bookings/create-class-service.md +3 -1
- package/skills/wix-manage/references/bookings/create-course-service.md +4 -2
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +68 -3
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
- package/skills/wix-manage/references/google-ads/billing-and-payment.md +3 -3
- package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +8 -8
- package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +4 -4
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +1 -1
- package/skills/wix-manage/references/google-ads/install-and-create-account.md +9 -9
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +108 -14
- package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +7 -7
- package/skills/wix-manage/references/seo/manage-content-plan.md +182 -136
- package/skills/wix-manage/references/seo/manage-google-search-console.md +272 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +283 -0
- package/skills/wix-manage/references/stores/change-store-currency.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +69 -27
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +32 -14
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +14 -3
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +41 -35
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +77 -30
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +31 -2
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +31 -6
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +0 -80
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +0 -140
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +0 -112
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +0 -100
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +0 -130
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +0 -48
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +0 -66
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +0 -103
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +0 -56
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +0 -23
|
@@ -133,33 +133,108 @@ function toDetail(raw: RawProduct): ProductDetail {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
// Search Products applies sort/filter/search server-side, across the WHOLE catalog, before
|
|
137
|
+
// cursor paging — the only correct place for them. Sorting or filtering an already-fetched
|
|
138
|
+
// page only rearranges the slice you happen to hold.
|
|
139
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
140
|
+
export const CATALOG_SORTS = {
|
|
141
|
+
featured: { label: "Default order" }, // the catalog's own order; no merchant-curated promise
|
|
142
|
+
priceAsc: { label: "Price: low to high" },
|
|
143
|
+
priceHigh: { label: "Price: high to low" },
|
|
144
|
+
name: { label: "Name: A\u2013Z" },
|
|
145
|
+
newest: { label: "Newest" },
|
|
146
|
+
} as const;
|
|
147
|
+
export type CatalogSort = keyof typeof CATALOG_SORTS;
|
|
148
|
+
|
|
149
|
+
const SORT_FIELDS: Partial<Record<CatalogSort, { fieldName: string; order: "ASC" | "DESC" }[]>> = {
|
|
150
|
+
priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
|
|
151
|
+
priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
152
|
+
name: [{ fieldName: "name", order: "ASC" }],
|
|
153
|
+
newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
function priceBound(value: number | string | undefined, name: string): number | undefined {
|
|
157
|
+
if (value == null || value === "") return undefined;
|
|
158
|
+
const parsed = Number(value);
|
|
159
|
+
if (!String(value).trim() || !Number.isFinite(parsed) || parsed < 0) {
|
|
160
|
+
throw new Error(`${name} must be a non-negative number.`);
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface CatalogSearchOptions {
|
|
166
|
+
limit?: number;
|
|
167
|
+
/** Continues the ORIGINAL query — pass alone; new sort/filter values start without one. */
|
|
168
|
+
cursor?: string;
|
|
169
|
+
categoryId?: string | null;
|
|
170
|
+
sort?: CatalogSort;
|
|
171
|
+
/** Bounds compare the product's minimum actual variant price, in site currency. */
|
|
172
|
+
minPrice?: number | string;
|
|
173
|
+
maxPrice?: number | string;
|
|
174
|
+
inStockOnly?: boolean;
|
|
175
|
+
/** Name search, max 100 chars. */
|
|
176
|
+
search?: string;
|
|
143
177
|
}
|
|
144
178
|
|
|
145
179
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* reconstruct it.
|
|
180
|
+
* Search visible catalog products — sorted, filtered, and searched by Wix across the whole
|
|
181
|
+
* catalog, then cursor-paged. `nextCursor` continues the same query; start over (no cursor)
|
|
182
|
+
* whenever any selection changes.
|
|
150
183
|
*/
|
|
184
|
+
export async function searchCatalog({
|
|
185
|
+
limit = 24,
|
|
186
|
+
cursor,
|
|
187
|
+
categoryId,
|
|
188
|
+
sort = "featured",
|
|
189
|
+
minPrice,
|
|
190
|
+
maxPrice,
|
|
191
|
+
inStockOnly = false,
|
|
192
|
+
search = "",
|
|
193
|
+
}: CatalogSearchOptions = {}): Promise<{ products: ProductSummary[]; nextCursor: string | null }> {
|
|
194
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
195
|
+
throw new Error("limit must be between 1 and 100.");
|
|
196
|
+
let query: RawProduct = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
|
|
197
|
+
if (!cursor) {
|
|
198
|
+
if (!(sort in CATALOG_SORTS)) throw new Error("Unsupported catalog sort.");
|
|
199
|
+
const min = priceBound(minPrice, "minPrice");
|
|
200
|
+
const max = priceBound(maxPrice, "maxPrice");
|
|
201
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
202
|
+
throw new Error("minPrice must not exceed maxPrice.");
|
|
203
|
+
if (search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
|
|
204
|
+
const conditions: RawProduct[] = [{ visible: true }];
|
|
205
|
+
if (categoryId)
|
|
206
|
+
conditions.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
|
|
207
|
+
// Search rejects two operators in one field object — separate bounds, joined by $and.
|
|
208
|
+
if (min !== undefined)
|
|
209
|
+
conditions.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
|
|
210
|
+
if (max !== undefined)
|
|
211
|
+
conditions.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
|
|
212
|
+
if (inStockOnly) conditions.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
|
|
213
|
+
query = {
|
|
214
|
+
...query,
|
|
215
|
+
filter: { $and: conditions },
|
|
216
|
+
...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
|
|
217
|
+
...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const res: RawProduct = await products.searchProducts(query, { fields: LIST_FIELDS as any });
|
|
221
|
+
return {
|
|
222
|
+
products: (res.products ?? []).map((p: RawProduct) => toSummary(p)),
|
|
223
|
+
nextCursor: res.pagingMetadata?.cursors?.next ?? null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** First page of visible products in the default order — a thin wrap of searchCatalog. */
|
|
228
|
+
export async function fetchProducts({ limit = 24 } = {}): Promise<ProductSummary[]> {
|
|
229
|
+
return (await searchCatalog({ limit })).products;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** First page of a category — same wrap; category filtering only works through search. */
|
|
151
233
|
export async function fetchProductsByCategory(
|
|
152
234
|
categoryId: string,
|
|
153
|
-
{ limit =
|
|
235
|
+
{ limit = 24 } = {},
|
|
154
236
|
): Promise<ProductSummary[]> {
|
|
155
|
-
|
|
156
|
-
{
|
|
157
|
-
filter: { "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } },
|
|
158
|
-
cursorPaging: { limit },
|
|
159
|
-
},
|
|
160
|
-
{ fields: LIST_FIELDS as any },
|
|
161
|
-
);
|
|
162
|
-
return (res.products ?? []).map((p: RawProduct) => toSummary(p));
|
|
237
|
+
return (await searchCatalog({ limit, categoryId })).products;
|
|
163
238
|
}
|
|
164
239
|
|
|
165
240
|
/** Fetch one product by its URL slug, with options/modifiers/variants. Null when not found. */
|
|
@@ -31,7 +31,8 @@ node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json
|
|
|
31
31
|
{ "name": "Backstage Guide", "description": "…", "price": 12,
|
|
32
32
|
"digitalFilePath": "/Users/me/guide.pdf" }
|
|
33
33
|
],
|
|
34
|
-
"categories": { "Legends": ["The Glam Rocker"], "Rising Stars": [] }
|
|
34
|
+
"categories": { "Legends": ["The Glam Rocker"], "Rising Stars": [] },
|
|
35
|
+
"currency": "EUR"
|
|
35
36
|
}
|
|
36
37
|
```
|
|
37
38
|
|
|
@@ -53,8 +54,20 @@ node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json
|
|
|
53
54
|
- `digitalFilePath` (a file on this machine) or `digitalFileUrl` — makes the product a digital
|
|
54
55
|
download, uploaded and created with both the file and stock (`quantity` is ignored). It's also the
|
|
55
56
|
only way in: a file-less digital product is created successfully, reads back healthy, and is then
|
|
56
|
-
rejected at add-to-cart as `ITEM_NOT_FOUND_IN_CATALOG`.
|
|
57
|
+
rejected at add-to-cart as `ITEM_NOT_FOUND_IN_CATALOG`. **No real file in hand?** Don't invent a
|
|
58
|
+
URL and don't ship the product as digital — seed it physical with stock (drop
|
|
59
|
+
`digitalFilePath`/`digitalFileUrl`, add `inStock` or a `quantity`) and tell the user the
|
|
60
|
+
download needs a real file before it can be sold.
|
|
57
61
|
- `categories` — category name → product NAMES. Omit when the brief names none.
|
|
62
|
+
- `quantity` — tracked stock, a non-negative integer. For stock that isn't counted (made to
|
|
63
|
+
order, print on demand, unlimited) use `"inStock": true` **instead** of `quantity`; sending
|
|
64
|
+
both is rejected.
|
|
65
|
+
- `currency` — 3-letter ISO code. Set it **only when the brief names one** ("prices in euros",
|
|
66
|
+
"a German store charging EUR"). Do **not** infer it from a language, a country, or an address
|
|
67
|
+
— an unrequested switch silently reprices the whole catalog. The seed applies it before
|
|
68
|
+
creating anything, because a product's price is stored in the site currency at create time.
|
|
69
|
+
For a few seconds afterwards product reads can still report the old currency; that lag is
|
|
70
|
+
expected and self-resolves, so don't re-verify it or retry.
|
|
58
71
|
|
|
59
72
|
**Default to 3 products** unless the brief asks for a specific catalog — the seed shows the
|
|
60
73
|
shape, not a full inventory; the owner adds the rest in the dashboard. **Make those 3 exercise
|
|
@@ -64,6 +77,12 @@ truthfully to the business (a ceramics studio has glaze colors; a bakery doesn't
|
|
|
64
77
|
**Seeding is additive — never delete or overwrite existing content.** No cleanup, no removing
|
|
65
78
|
"sample" data, no resets. If a cleanup genuinely seems needed, ask the user first.
|
|
66
79
|
|
|
80
|
+
**A bulk create can partially succeed.** The result carries `failures: [{ name, error }]` next
|
|
81
|
+
to `products` — read it. A non-empty `failures` means those products are genuinely absent, not
|
|
82
|
+
mis-mapped, so the rest of the catalog is fine to build on. To retry, re-run the **same** plan:
|
|
83
|
+
creation is idempotent by name, so products that already exist are skipped rather than
|
|
84
|
+
duplicated. Never hand-patch ids to "fill the gap".
|
|
85
|
+
|
|
67
86
|
Two things this module does not seed (dashboard-only — tell the merchant):
|
|
68
87
|
**ribbons** ("New", "Best Seller") and **per-choice linked media** (color choice → gallery photo).
|
|
69
88
|
|
|
@@ -162,7 +162,7 @@ function buildOptions(options = []) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// Full Cartesian product, each variant priced/stocked from the product; visible:true baked in.
|
|
165
|
-
function expandVariants(options = [], { price, compareAtPrice, quantity }, digitalFileId) {
|
|
165
|
+
function expandVariants(options = [], { price, compareAtPrice, quantity, inStock }, digitalFileId) {
|
|
166
166
|
const base = {
|
|
167
167
|
price: {
|
|
168
168
|
actualPrice: { amount: String(price) },
|
|
@@ -171,7 +171,10 @@ function expandVariants(options = [], { price, compareAtPrice, quantity }, digit
|
|
|
171
171
|
visible: true,
|
|
172
172
|
...(digitalFileId
|
|
173
173
|
? { digitalProperties: { digitalFile: { id: digitalFileId } }, inventoryItem: { inStock: true } }
|
|
174
|
-
|
|
174
|
+
// inStock:true == untracked stock — always buyable, no count. Otherwise track a quantity.
|
|
175
|
+
: { physicalProperties: {}, inventoryItem: inStock === true
|
|
176
|
+
? { inStock: true }
|
|
177
|
+
: { quantity: quantity ?? 0, preorderInfo: { enabled: false } } }),
|
|
175
178
|
};
|
|
176
179
|
if (!options.length) return [base];
|
|
177
180
|
let combos = [[]];
|
|
@@ -235,7 +238,10 @@ async function uploadDigitalFile(ctx, { digitalFileUrl, digitalFilePath, digital
|
|
|
235
238
|
const bytes = digitalFilePath
|
|
236
239
|
? readFileSync(digitalFilePath)
|
|
237
240
|
: await fetch(digitalFileUrl).then((r) => {
|
|
238
|
-
|
|
241
|
+
// Don't invent a file and don't ship an unbuyable DIGITAL product: seed it PHYSICAL
|
|
242
|
+
// with stock (drop digitalFileUrl/digitalFilePath, set inStock or a quantity) and
|
|
243
|
+
// tell the user the download still needs a real file.
|
|
244
|
+
if (!r.ok) throw new Error(`digitalFileUrl ${digitalFileUrl} -> ${r.status}. No fetchable file: re-seed this product as PHYSICAL with stock and tell the user it needs a real file before it can be sold as a download.`);
|
|
239
245
|
return r.arrayBuffer();
|
|
240
246
|
});
|
|
241
247
|
const { uploadUrl } = await req(ctx, "/site-media/v1/files/generate-upload-url", { body: { mimeType, fileName } });
|
|
@@ -266,15 +272,36 @@ export async function bulkCreateProducts(ctx, products) {
|
|
|
266
272
|
};
|
|
267
273
|
const r = await req(ctx, "/stores/v3/bulk/products-with-inventory/create", { body });
|
|
268
274
|
// NB: results nest under productResults.results[].item — NOT a top-level `results`.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
// The bulk returns 200 even on PARTIAL failure, so never map results by array position:
|
|
276
|
+
// pair each result to its input via itemMetadata.originalIndex and drop the ones that
|
|
277
|
+
// didn't persist. Positional mapping shifts every id after a failure onto the wrong
|
|
278
|
+
// product — which then mislabels categories and attaches images to the wrong items.
|
|
279
|
+
const created = [];
|
|
280
|
+
const failures = [];
|
|
281
|
+
for (const x of r.productResults?.results ?? []) {
|
|
282
|
+
const i = x.itemMetadata?.originalIndex;
|
|
283
|
+
const src = typeof i === "number" ? products[i] : undefined;
|
|
284
|
+
if (!x.itemMetadata?.success || !x.item?.id) {
|
|
285
|
+
failures.push({
|
|
286
|
+
name: src?.name,
|
|
287
|
+
error: x.itemMetadata?.error?.description ?? x.itemMetadata?.error?.code ?? "unknown",
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
created.push({
|
|
292
|
+
id: x.item.id, slug: x.item.slug, revision: x.item.revision, name: src?.name,
|
|
293
|
+
variantId: x.item.variantsInfo?.variants?.[0]?.id,
|
|
294
|
+
hasOptions: (src?.options?.length ?? 0) > 0,
|
|
295
|
+
isDigital: !!fileIds[i],
|
|
296
|
+
quantity: src?.quantity ?? 0,
|
|
297
|
+
inStock: src?.inStock,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
276
300
|
await stockOptionlessProducts(ctx, created);
|
|
277
|
-
return
|
|
301
|
+
return {
|
|
302
|
+
created: created.map((p) => ({ id: p.id, slug: p.slug, revision: p.revision, name: p.name })),
|
|
303
|
+
failures,
|
|
304
|
+
};
|
|
278
305
|
}
|
|
279
306
|
|
|
280
307
|
// The bulk create stocks a variant via its choices; an OPTION-LESS product's single default
|
|
@@ -290,9 +317,15 @@ async function stockOptionlessProducts(ctx, created) {
|
|
|
290
317
|
const vById = new Map((q.products ?? []).map((p) => [p.id, p.variantsInfo?.variants?.[0]?.id]));
|
|
291
318
|
need.forEach((p) => { if (!p.variantId) p.variantId = vById.get(p.id); });
|
|
292
319
|
}
|
|
320
|
+
// inStock:true == untracked stock (always buyable, no count). Only send a quantity when the
|
|
321
|
+
// product actually tracks one, or Wix rejects the pair.
|
|
293
322
|
const inventoryItems = need
|
|
294
323
|
.filter((p) => p.variantId)
|
|
295
|
-
.map((p) => ({
|
|
324
|
+
.map((p) => ({
|
|
325
|
+
productId: p.id,
|
|
326
|
+
variantId: p.variantId,
|
|
327
|
+
...(p.inStock === true ? { inStock: true } : { quantity: p.quantity }),
|
|
328
|
+
}));
|
|
296
329
|
if (inventoryItems.length) {
|
|
297
330
|
await req(ctx, "/stores/v3/bulk/inventory-items/create", { body: { inventoryItems } });
|
|
298
331
|
}
|
|
@@ -342,20 +375,68 @@ export async function attachProductImages(ctx, items) {
|
|
|
342
375
|
});
|
|
343
376
|
}
|
|
344
377
|
|
|
378
|
+
// Reject plans the API would reject halfway through, while nothing has been created yet —
|
|
379
|
+
// a mid-batch 400 leaves a half-seeded store that the agent then has to reason about.
|
|
380
|
+
export function validateProducts(products) {
|
|
381
|
+
const problems = [];
|
|
382
|
+
const colorByName = new Map();
|
|
383
|
+
products.forEach((p, i) => {
|
|
384
|
+
const where = p.name ? `"${p.name}"` : `product #${i + 1}`;
|
|
385
|
+
if (!p.name) problems.push(`${where}: name is required`);
|
|
386
|
+
if (p.quantity != null && (!Number.isInteger(p.quantity) || p.quantity < 0)) {
|
|
387
|
+
problems.push(`${where}: quantity must be a non-negative integer (got ${p.quantity}) — omit it and set inStock:true for untracked stock`);
|
|
388
|
+
}
|
|
389
|
+
for (const opt of p.options ?? []) {
|
|
390
|
+
const seen = new Set();
|
|
391
|
+
for (const c of opt.choices ?? []) {
|
|
392
|
+
const key = typeof c === "string" ? c : c?.name;
|
|
393
|
+
if (seen.has(key)) problems.push(`${where}: option "${opt.name}" repeats the choice "${key}"`);
|
|
394
|
+
seen.add(key);
|
|
395
|
+
// Wix keys a color choice by name, so the same name with two codes collides.
|
|
396
|
+
const code = typeof c === "object" ? c?.colorCode : undefined;
|
|
397
|
+
if (code) {
|
|
398
|
+
const prev = colorByName.get(key);
|
|
399
|
+
if (prev && prev !== code) problems.push(`color "${key}" is ${prev} on one product and ${code} on another — pick one`);
|
|
400
|
+
colorByName.set(key, code);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
if (problems.length) throw new Error(`invalid seed plan:\n - ${problems.join("\n - ")}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Site currency, set BEFORE any product exists (see setupStore). Existing product reads can
|
|
409
|
+
// keep reporting the old currency for a short while after this returns — that lag is expected
|
|
410
|
+
// and self-resolves, so don't re-verify or retry on it.
|
|
411
|
+
// docs: https://dev.wix.com/docs/rest/business-management/site-properties/properties/update-site-properties
|
|
412
|
+
async function setSiteCurrency(ctx, currency) {
|
|
413
|
+
await req(ctx, "/site-properties/v4/properties", {
|
|
414
|
+
method: "PATCH",
|
|
415
|
+
body: { properties: { paymentCurrency: currency }, fields: ["paymentCurrency"] },
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
345
419
|
/**
|
|
346
|
-
* ONE-CALL seed: install → create products → categories → attach images, ids
|
|
347
|
-
* memory. This is the default path — call it once instead of the individual
|
|
420
|
+
* ONE-CALL seed: install → currency → create products → categories → attach images, ids
|
|
421
|
+
* threaded in memory. This is the default path — call it once instead of the individual
|
|
422
|
+
* functions.
|
|
348
423
|
*/
|
|
349
|
-
export async function setupStore(ctx, { products = [], categories = {} } = {}) {
|
|
424
|
+
export async function setupStore(ctx, { products = [], categories = {}, currency } = {}) {
|
|
425
|
+
validateProducts(products);
|
|
350
426
|
await installStoresApp(ctx);
|
|
427
|
+
// Before any product exists: a product's price is stored in the site currency at create time,
|
|
428
|
+
// so switching afterwards leaves the catalog priced in the old one.
|
|
429
|
+
if (currency) await setSiteCurrency(ctx, currency);
|
|
351
430
|
|
|
352
431
|
// Idempotent by name: an errored bulk create (429/5xx) may still have applied server-side,
|
|
353
432
|
// and SKILL.md tells the agent to re-run a failed seed — creating only the names that don't
|
|
354
433
|
// exist yet makes that rerun safe instead of a duplicator.
|
|
355
434
|
const existing = await queryProductsByNames(ctx, products.map((p) => p.name));
|
|
356
435
|
const toCreate = products.filter((p) => !existing.has(p.name));
|
|
357
|
-
const created = toCreate.length
|
|
358
|
-
|
|
436
|
+
const { created, failures } = toCreate.length
|
|
437
|
+
? await bulkCreateProducts(ctx, toCreate)
|
|
438
|
+
: { created: [], failures: [] };
|
|
439
|
+
const createdByName = new Map(created.map((p) => [p.name, p]));
|
|
359
440
|
const withNames = products.map((p) => {
|
|
360
441
|
const hit = createdByName.get(p.name) ?? existing.get(p.name);
|
|
361
442
|
return { ...(hit ?? {}), name: p.name };
|
|
@@ -381,6 +462,8 @@ export async function setupStore(ctx, { products = [], categories = {} } = {}) {
|
|
|
381
462
|
prompt: products[i]?.imagePrompt,
|
|
382
463
|
displayName: `${p.slug || "product"}.png`,
|
|
383
464
|
})));
|
|
465
|
+
// `p.id` guards this: a product that failed to create has no id, and bulk-updating an
|
|
466
|
+
// undefined id would 400 the whole batch and cost every other product its image.
|
|
384
467
|
const imageItems = withNames
|
|
385
468
|
.map((p, i) => (files[i] && p.id ? { id: p.id, url: files[i].url, altText: products[i]?.altText ?? p.slug } : null))
|
|
386
469
|
.filter(Boolean);
|
|
@@ -392,7 +475,10 @@ export async function setupStore(ctx, { products = [], categories = {} } = {}) {
|
|
|
392
475
|
/* never block on image failure — the products stay text-only */
|
|
393
476
|
}
|
|
394
477
|
|
|
395
|
-
|
|
478
|
+
// failures is part of the result, not an exception: a partial seed still leaves a usable
|
|
479
|
+
// store, and the agent needs the names to report rather than silently shipping a short
|
|
480
|
+
// catalog. Re-run the seed to retry them — existing names are skipped, not duplicated.
|
|
481
|
+
return { products: withNames, categories: cats, imagesAttached, failures };
|
|
396
482
|
}
|
|
397
483
|
|
|
398
484
|
// ---- CLI entry ----------------------------------------------------------------------------------
|
|
@@ -45,6 +45,9 @@ Read and update SEO titles and tags at the right level. For "change my site's SE
|
|
|
45
45
|
### [Generate and Read a Wix Site's Content Plan](references/seo/manage-content-plan.md)
|
|
46
46
|
Generate an SEO content plan and read its blog post topics, or troubleshoot an existing content plan flow stuck at KEYWORD_RESEARCH while polling GetContentPlanFlow. Use this recipe for both generation and stalled-flow questions: it explains the intentional pause, the Create Content Plan release request, missing flow IDs, and the exact public API paths and response fields.
|
|
47
47
|
|
|
48
|
+
### [Manage Google Search Console for a Wix Site](references/seo/manage-google-search-console.md)
|
|
49
|
+
"Connect a Wix site to Google Search Console and drive its setup through the public GSC Connection and Site Readiness APIs: check connection and readiness, start the Google authorization, verify ownership, add the property, submit the sitemap, request indexing, read search performance, run URL inspection, recover a stale connection, or disconnect. The site owner authorizes in their own browser via a single-use connect URL."
|
|
50
|
+
|
|
48
51
|
---
|
|
49
52
|
|
|
50
53
|
## Accessibility
|
|
@@ -104,7 +107,7 @@ Full CRUD operations for Wix Bookings services using Services API. Covers servic
|
|
|
104
107
|
"Answers whether an appointment-based Wix Bookings service currently has bookable availability — the primary question — and diagnoses the cause only when there's no availability or the owner asks why. To diagnose, first rules out service-level blockers the availability endpoint can't see (service hidden, online booking off), then runs DiagnoseAvailability for ordered, machine-readable staff/setup reasons, with a manual fallback for booking-policy and capacity causes. Use when someone asks whether a service has availability, or why a service shows no times / customers can't book it."
|
|
105
108
|
|
|
106
109
|
### [End-to-End Booking Flow](references/bookings/end-to-end-booking-flow.md)
|
|
107
|
-
|
|
110
|
+
Books and settles appointments, classes and courses with the site owner's credentials — an operator managing bookings, or server-side code booking as the owner. Covers service discovery, availability with Time Slots V2, creating the booking, and settling it by direct confirmation or eCommerce checkout. A visitor booking for themselves needs a visitor token instead; this recipe links that path.
|
|
108
111
|
|
|
109
112
|
### [External Calendar Integration](references/bookings/external-calendar-integration.md)
|
|
110
113
|
OAuth-based integration with Google Calendar, Microsoft Outlook, and Apple Calendar. Covers authentication flows, sync configuration, and bidirectional event management.
|
|
@@ -283,35 +286,35 @@ Creates payment links for unpaid bookings using Payment Links API. Links booking
|
|
|
283
286
|
|
|
284
287
|
## Google Ads
|
|
285
288
|
|
|
286
|
-
**Routing — Google paid-advertising campaigns for a site (Smart & Performance Max).** All flows require a Google Ads account, created once via the setup recipe. Budgets are in micros (1,000,000 = 1 currency unit). REST base: `https://www.wixapis.com/google
|
|
289
|
+
**Routing — Google paid-advertising campaigns for a site (Smart & Performance Max).** All flows require a Google Ads account, created once via the setup recipe. Budgets are in micros (1,000,000 = 1 currency unit). REST base: `https://www.wixapis.com/_serverless/pa-google/v1`.
|
|
287
290
|
- **First-time setup / "connect Google Ads" / `ACCOUNT_NOT_FOUND`** → [Install and Create an Account](references/google-ads/install-and-create-account.md) (do this before anything else).
|
|
288
291
|
- **Suggested keywords / geo / budget / ad copy / images** → [Get AI Campaign Suggestions](references/google-ads/get-campaign-suggestions.md).
|
|
289
|
-
- **
|
|
292
|
+
- **Improve an existing campaign / see what to fix next / Campaign Success Guide / mark or reopen a recommendation** → [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md).
|
|
290
293
|
- **Create a multi-channel / lead-gen / Shopping campaign** → [Create a Performance Max Campaign](references/google-ads/create-performance-max-campaign.md).
|
|
291
294
|
- **Pause / resume / launch / update budget / delete / history** → [Manage Campaign Lifecycle](references/google-ads/manage-campaign-lifecycle.md).
|
|
292
295
|
- **Performance, conversions, search terms, per-product / per-asset metrics** → [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md).
|
|
293
296
|
- **Ad spend, fees, upcoming charges, credit balance** → [Retrieve Billing and Payment Details](references/google-ads/billing-and-payment.md).
|
|
294
297
|
|
|
295
298
|
### [Install Google Ads and Create an Account](references/google-ads/install-and-create-account.md)
|
|
296
|
-
"One-time setup for running Google paid ads on a Wix site: install the Wix Google Ads app, then create the Google Ads account that every campaign, suggestion, and analytics call depends on. Covers checking whether an account already exists, choosing a currency, optionally attaching a promotional incentive (credit offer), linking a Google Merchant Center account, and deleting an account. Use when the user wants to 'set up Google Ads', 'connect Google Ads', 'start advertising on Google', 'create a Google Ads account', or hits an ACCOUNT_NOT_FOUND / app-not-installed error before creating a campaign. Google Ads REST API, base https://www.wixapis.com/google
|
|
299
|
+
"One-time setup for running Google paid ads on a Wix site: install the Wix Google Ads app, then create the Google Ads account that every campaign, suggestion, and analytics call depends on. Covers checking whether an account already exists, choosing a currency, optionally attaching a promotional incentive (credit offer), linking a Google Merchant Center account, and deleting an account. Use when the user wants to 'set up Google Ads', 'connect Google Ads', 'start advertising on Google', 'create a Google Ads account', or hits an ACCOUNT_NOT_FOUND / app-not-installed error before creating a campaign. Google Ads REST API, base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
297
300
|
|
|
298
301
|
### [Get AI Campaign Suggestions for Google Ads](references/google-ads/get-campaign-suggestions.md)
|
|
299
|
-
"Reference for the Google Ads Suggestions API on a Wix site: AI/Google-generated inputs that help build effective campaigns — keyword themes (from a URL or autocomplete), geo-target options, low/recommended/high daily-budget tiers with estimated clicks, PMAX budget recommendations, text assets (headlines/descriptions), AI image assets (auto-uploaded to Wix Media), search themes, promotional incentive offers, and complete AI-generated campaign configurations from a campaign brief. Use when the user asks 'suggest keywords for my ads', 'what budget should I use', 'where should I target', 'generate ad copy/headlines', 'generate ad images', 'suggest a whole campaign', or when a create-campaign flow needs suggested values. REST base https://www.wixapis.com/google
|
|
302
|
+
"Reference for the Google Ads Suggestions API on a Wix site: AI/Google-generated inputs that help build effective campaigns — keyword themes (from a URL or autocomplete), geo-target options, low/recommended/high daily-budget tiers with estimated clicks, PMAX budget recommendations, text assets (headlines/descriptions), AI image assets (auto-uploaded to Wix Media), search themes, promotional incentive offers, and complete AI-generated campaign configurations from a campaign brief. Use when the user asks 'suggest keywords for my ads', 'what budget should I use', 'where should I target', 'generate ad copy/headlines', 'generate ad images', 'suggest a whole campaign', or when a create-campaign flow needs suggested values. REST base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
300
303
|
|
|
301
304
|
### [Create and Launch a Performance Max Campaign](references/google-ads/create-performance-max-campaign.md)
|
|
302
|
-
"Creates and launches a Google Ads Performance Max (PMAX) campaign for a Wix site — a goal-based campaign that runs across all Google channels (Search, Display, YouTube, Gmail, Discover, Maps) from an asset group of headlines, descriptions, images, and (for PMAX Leads) search-theme signals. Covers generating AI text and image assets, generating search themes, getting a Google budget recommendation, assembling the asset group with the required minimum assets, choosing PERFORMANCE_MAX vs PERFORMANCE_MAX_LEADS (leads: phone/form goals, negative keywords, 28-day learning) vs retail/Shopping (Merchant Center feed), creating in PAUSED, and launching. Use for 'create a Performance Max campaign', 'PMAX', 'run ads across all of Google', 'lead-gen Google campaign', or 'Google Shopping ads'. Requires an existing Google Ads account. REST base https://www.wixapis.com/google
|
|
305
|
+
"Creates and launches a Google Ads Performance Max (PMAX) campaign for a Wix site — a goal-based campaign that runs across all Google channels (Search, Display, YouTube, Gmail, Discover, Maps) from an asset group of headlines, descriptions, images, and (for PMAX Leads) search-theme signals. Covers generating AI text and image assets, generating search themes, getting a Google budget recommendation, assembling the asset group with the required minimum assets, choosing PERFORMANCE_MAX vs PERFORMANCE_MAX_LEADS (leads: phone/form goals, negative keywords, 28-day learning) vs retail/Shopping (Merchant Center feed), creating in PAUSED, and launching. Use for 'create a Performance Max campaign', 'PMAX', 'run ads across all of Google', 'lead-gen Google campaign', or 'Google Shopping ads'. Requires an existing Google Ads account. REST base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
303
306
|
|
|
304
307
|
### [Manage Campaign Lifecycle](references/google-ads/manage-campaign-lifecycle.md)
|
|
305
|
-
"Manages existing Google Ads campaigns on a Wix site: list/get, launch (first activation) vs resume (reactivate after a pause), pause a running campaign — optionally with a scheduled auto-resume date — update name/budget/targeting, change the daily budget, delete permanently, and read status history / change log. Use when the user wants to 'pause my Google ad', 'resume my campaign', 'stop the campaign', 'change my daily budget', 'rename the campaign', 'delete this campaign', 'list my Google Ads campaigns', or 'why did my campaign status change'. Requires an existing Google Ads account and campaign. REST base https://www.wixapis.com/google
|
|
308
|
+
"Manages existing Google Ads campaigns on a Wix site: list/get, launch (first activation) vs resume (reactivate after a pause), pause a running campaign — optionally with a scheduled auto-resume date — update name/budget/targeting, change the daily budget, delete permanently, and read status history / change log. Use when the user wants to 'pause my Google ad', 'resume my campaign', 'stop the campaign', 'change my daily budget', 'rename the campaign', 'delete this campaign', 'list my Google Ads campaigns', or 'why did my campaign status change'. Requires an existing Google Ads account and campaign. REST base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
306
309
|
|
|
307
310
|
### [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md)
|
|
308
|
-
"
|
|
311
|
+
"Campaign Success Guide for existing Wix Google Ads Performance Max Leads campaigns. Use after creating a supported campaign, even while it is learning or has no metrics, and whenever users ask how to improve a campaign, what to fix next, to view the guide, or to mark or reopen a recommendation. Covers campaign and site selection; prioritized actionable recommendations; deduplicated, destination-specific Editor and Google Ads navigation; offers to perform supported work after approval; Merchant Center and Business Profile connection follow-ups; and suggestion-status tracking."
|
|
309
312
|
|
|
310
313
|
### [Query Campaign Performance Analytics](references/google-ads/query-campaign-analytics.md)
|
|
311
|
-
"Reads performance analytics for a Google Ads campaign on a Wix site: daily performance metrics (impressions, clicks, CTR, cost, leads, phone calls) with optional previous-period comparison and trends; conversion metrics from Wix Analytics (orders, revenue, leads, CPL, ROAS); the search terms that triggered a campaign's ads; per-product shopping performance for retail campaigns; and per-asset performance (headlines, descriptions, images) for PMAX Leads. Explains when to use campaignResourceName vs the Wix campaignId, the dateRange shape, field enums, sorting, and paging. Use when the user asks 'how is my campaign doing', 'show ad performance', 'what search terms triggered my ads', 'which products/assets perform best', 'campaign ROI/ROAS', or 'conversions from my Google ads'. REST base https://www.wixapis.com/google
|
|
314
|
+
"Reads performance analytics for a Google Ads campaign on a Wix site: daily performance metrics (impressions, clicks, CTR, cost, leads, phone calls) with optional previous-period comparison and trends; conversion metrics from Wix Analytics (orders, revenue, leads, CPL, ROAS); the search terms that triggered a campaign's ads; per-product shopping performance for retail campaigns; and per-asset performance (headlines, descriptions, images) for PMAX Leads. Explains when to use campaignResourceName vs the Wix campaignId, the dateRange shape, field enums, sorting, and paging. Use when the user asks 'how is my campaign doing', 'show ad performance', 'what search terms triggered my ads', 'which products/assets perform best', 'campaign ROI/ROAS', or 'conversions from my Google ads'. REST base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
312
315
|
|
|
313
316
|
### [Retrieve Google Ads Billing and Payment Details](references/google-ads/billing-and-payment.md)
|
|
314
|
-
"Retrieves billing and payment details for a Wix site's Google Ads account: the current billing period's ad spend (usage), the Wix service fee, the total charge, any promotional coupon adjustment, the billing period dates, and the account's credit balance (positive = available credits, negative = outstanding debt not yet charged). Also explains reading current vs remaining budget from the account object. Use when the user asks 'how much have I spent on Google Ads', 'what's my next Google Ads charge', 'show my ad billing', 'do I have ad credits left', 'why was I charged', or 'upcoming Google Ads payment'. Requires an existing Google Ads account. REST base https://www.wixapis.com/google
|
|
317
|
+
"Retrieves billing and payment details for a Wix site's Google Ads account: the current billing period's ad spend (usage), the Wix service fee, the total charge, any promotional coupon adjustment, the billing period dates, and the account's credit balance (positive = available credits, negative = outstanding debt not yet charged). Also explains reading current vs remaining budget from the account object. Use when the user asks 'how much have I spent on Google Ads', 'what's my next Google Ads charge', 'show my ad billing', 'do I have ad credits left', 'why was I charged', or 'upcoming Google Ads payment'. Requires an existing Google Ads account. REST base https://www.wixapis.com/_serverless/pa-google/v1."
|
|
315
318
|
|
|
316
319
|
### [Google Ads Dashboard Navigation](references/google-ads/google-ads-dashboard-navigation.md)
|
|
317
320
|
"Builds a direct link to the Wix Google Ads dashboard page on manage.wix.com, where campaigns created via the Google Ads API recipes are managed. Use when the user asks where something is in the Wix dashboard, wants a direct link to a dashboard page, or you need a dashboard URL to include with the result of an API operation."
|
|
@@ -421,6 +424,9 @@ Drive the Wix Site Import agent to migrate an existing store or site from anothe
|
|
|
421
424
|
### [Sites Dashboard Navigation](references/sites/sites-dashboard-navigation.md)
|
|
422
425
|
"Builds direct links to the account-level sites pages on manage.wix.com — the My Sites list (all sites in the account) and each site's own dashboard. Pairs the site list with the Query Sites read API. Use when the user asks where something is in the Wix dashboard, wants a direct link to a dashboard page, or you need a dashboard URL to include with the result of an API operation."
|
|
423
426
|
|
|
427
|
+
### [Upload a Website or HTML Files](references/sites/upload-static-site.md)
|
|
428
|
+
Publish a user's ready-made website — an index.html, a static build, or a zip exported from an AI builder or any other tool — as a new live Wix site. Covers both ways to get there — handing the user the Wix Headless drop page, and calling the Wix Headless instant-site REST API yourself when you can reach the files and make outbound HTTPS requests. When you also hold the user's identity, covers putting the published site straight into their Wix account and reading back its final live URL. Use whenever the user wants to upload, publish, deploy, or host their own HTML/CSS/JS as a NEW site, including files generated for them earlier in the conversation, or to update a site already published this way (iterate on the same site instead of creating another). Not for migrating a live store/site from another platform by URL or from CSV exports (use Site Import), not for adding HTML or custom code into an existing Wix site, and not for uploading images or documents to a site's media files.
|
|
429
|
+
|
|
424
430
|
---
|
|
425
431
|
|
|
426
432
|
## Stores
|
|
@@ -201,6 +201,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
201
201
|
-H 'Authorization: <AUTH>' \
|
|
202
202
|
-H 'Content-Type: application/json' \
|
|
203
203
|
-d '{
|
|
204
|
+
"returnEntity": true,
|
|
204
205
|
"services": [{
|
|
205
206
|
"name": "Consultation",
|
|
206
207
|
"type": "APPOINTMENT",
|
|
@@ -234,6 +235,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
234
235
|
-H 'Authorization: <AUTH>' \
|
|
235
236
|
-H 'Content-Type: application/json' \
|
|
236
237
|
-d '{
|
|
238
|
+
"returnEntity": true,
|
|
237
239
|
"services": [{
|
|
238
240
|
"name": "Free Consultation",
|
|
239
241
|
"type": "APPOINTMENT",
|
|
@@ -264,6 +266,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
264
266
|
-H 'Authorization: <AUTH>' \
|
|
265
267
|
-H 'Content-Type: application/json' \
|
|
266
268
|
-d '{
|
|
269
|
+
"returnEntity": true,
|
|
267
270
|
"services": [{
|
|
268
271
|
"name": "Yoga Class",
|
|
269
272
|
"type": "CLASS",
|
|
@@ -283,7 +286,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
283
286
|
}'
|
|
284
287
|
```
|
|
285
288
|
|
|
286
|
-
After creation, use
|
|
289
|
+
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`).
|
|
287
290
|
|
|
288
291
|
**Required Fields:**
|
|
289
292
|
|
|
@@ -109,6 +109,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
109
109
|
-H 'Authorization: <AUTH>' \
|
|
110
110
|
-H 'Content-Type: application/json' \
|
|
111
111
|
-d '{
|
|
112
|
+
"returnEntity": true,
|
|
112
113
|
"services": [{
|
|
113
114
|
"name": "<SERVICE_NAME>",
|
|
114
115
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -142,6 +143,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
142
143
|
-H 'Authorization: <AUTH>' \
|
|
143
144
|
-H 'Content-Type: application/json' \
|
|
144
145
|
-d '{
|
|
146
|
+
"returnEntity": true,
|
|
145
147
|
"services": [{
|
|
146
148
|
"name": "<SERVICE_NAME>",
|
|
147
149
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -171,7 +173,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
171
173
|
- `schedule.availabilityConstraints.sessionDurations` sets the appointment length
|
|
172
174
|
- Availability is based on the assigned staff member's working hours schedule
|
|
173
175
|
|
|
174
|
-
Save the `serviceId` from the response: `results[0].item.service.id
|
|
176
|
+
Save the `serviceId` from the response: `results[0].item.id` (the created service is directly under `item` — there is no `item.service`). This requires `returnEntity: true` on the request; without it the response carries only `results[0].itemMetadata.id`.
|
|
175
177
|
|
|
176
178
|
---
|
|
177
179
|
|
|
@@ -96,6 +96,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
96
96
|
-H 'Authorization: <AUTH>' \
|
|
97
97
|
-H 'Content-Type: application/json' \
|
|
98
98
|
-d '{
|
|
99
|
+
"returnEntity": true,
|
|
99
100
|
"services": [{
|
|
100
101
|
"name": "<SERVICE_NAME>",
|
|
101
102
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -124,6 +125,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
124
125
|
-H 'Authorization: <AUTH>' \
|
|
125
126
|
-H 'Content-Type: application/json' \
|
|
126
127
|
-d '{
|
|
128
|
+
"returnEntity": true,
|
|
127
129
|
"services": [{
|
|
128
130
|
"name": "<SERVICE_NAME>",
|
|
129
131
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -149,7 +151,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
149
151
|
- `defaultCapacity` is **required** — sets max participants per session
|
|
150
152
|
- After creation, class sessions must be scheduled separately via `bulkCreateEvents` using the returned `service.schedule.id` (see [Create and Update Booking Services](./create-and-update-booking-services.md))
|
|
151
153
|
|
|
152
|
-
**Response:** Save the service ID from `results[0].itemMetadata.id`
|
|
154
|
+
**Response:** Save the service ID from `results[0].item.id` and the schedule ID for `bulkCreateEvents` from `results[0].item.schedule.id` (the created service is directly under `item`). This requires `returnEntity: true` on the request — without it the response carries only `results[0].itemMetadata.id`, which has no `schedule.id`.
|
|
153
155
|
|
|
154
156
|
---
|
|
155
157
|
|
|
@@ -101,6 +101,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
101
101
|
-H 'Authorization: <AUTH>' \
|
|
102
102
|
-H 'Content-Type: application/json' \
|
|
103
103
|
-d '{
|
|
104
|
+
"returnEntity": true,
|
|
104
105
|
"services": [{
|
|
105
106
|
"name": "<SERVICE_NAME>",
|
|
106
107
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -129,6 +130,7 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
129
130
|
-H 'Authorization: <AUTH>' \
|
|
130
131
|
-H 'Content-Type: application/json' \
|
|
131
132
|
-d '{
|
|
133
|
+
"returnEntity": true,
|
|
132
134
|
"services": [{
|
|
133
135
|
"name": "<SERVICE_NAME>",
|
|
134
136
|
"description": "<GENERATED_DESCRIPTION>",
|
|
@@ -157,8 +159,8 @@ curl -X POST 'https://www.wixapis.com/bookings/v2/bulk/services/create' \
|
|
|
157
159
|
- After creation, course sessions must be scheduled separately via `bulkCreateEvents` using the returned `service.schedule.id` (see [Create and Update Booking Services](./create-and-update-booking-services.md))
|
|
158
160
|
- If the user asked you to create the course and gave the session count/times, the job is not complete after `bulkCreateServices`; continue by creating the Calendar events, then verify the service has future events before telling the user it is ready
|
|
159
161
|
|
|
160
|
-
Save the `serviceId` from the response: `results[0].item.service.id
|
|
161
|
-
Save the `service.schedule.id` from the response for the follow-up `bulkCreateEvents` request.
|
|
162
|
+
Save the `serviceId` from the response: `results[0].item.id` (the created service is directly under `item` — there is no `item.service`). This requires `returnEntity: true` on the request; without it the response carries only `results[0].itemMetadata.id`.
|
|
163
|
+
Save the `service.schedule.id` from the response (`results[0].item.schedule.id`) for the follow-up `bulkCreateEvents` request.
|
|
162
164
|
|
|
163
165
|
---
|
|
164
166
|
|