@wix/agent-skills 1.19.0 → 1.19.1

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 (58) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +33 -31
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +6 -10
  4. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +16 -7
  5. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +53 -131
  6. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +1 -2
  7. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +83 -73
  8. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +3 -2
  9. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +9 -0
  10. package/skills/wix-app/references/editor-react-component/PARTS.md +5 -2
  11. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +1 -1
  12. package/skills/wix-app/scripts/pkg-root.cjs +73 -0
  13. package/skills/wix-app/scripts/scan-a11y-code.cjs +310 -40
  14. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +118 -24
  15. package/skills/wix-app/scripts/scan-a11y-render.cjs +551 -0
  16. package/skills/wix-app/scripts/scan-a11y-review.cjs +373 -0
  17. package/skills/wix-base44-connector/SKILL.md +24 -22
  18. package/skills/wix-base44-connector/scripts/utils.cjs +39 -3
  19. package/skills/wix-docs/SKILL.md +47 -20
  20. package/skills/wix-headless/entry/bootstrap.mjs +106 -50
  21. package/skills/wix-headless/entry/skill.md +5 -3
  22. package/skills/wix-headless/references/managed/AUTHENTICATION.md +25 -5
  23. package/skills/wix-headless-fast/SKILL.md +25 -7
  24. package/skills/wix-headless-fast/entry/skill.md +23 -14
  25. package/skills/wix-headless-fast/install/fast-path.mjs +48 -5
  26. package/skills/wix-headless-fast/install/pin-agents-md.mjs +57 -0
  27. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +3 -3
  28. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +204 -35
  29. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +146 -36
  30. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +8 -2
  31. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +95 -20
  32. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +21 -2
  33. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +104 -18
  34. package/skills/wix-manage/SKILL.md +13 -7
  35. package/skills/wix-manage/references/google-ads/billing-and-payment.md +3 -3
  36. package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +8 -8
  37. package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +4 -4
  38. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +1 -1
  39. package/skills/wix-manage/references/google-ads/install-and-create-account.md +9 -9
  40. package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +7 -7
  41. package/skills/wix-manage/references/seo/manage-content-plan.md +181 -135
  42. package/skills/wix-manage/references/seo/manage-google-search-console.md +272 -0
  43. package/skills/wix-manage/references/sites/upload-static-site.md +53 -0
  44. package/skills/wix-manage/references/stores/change-store-currency.md +1 -1
  45. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +3 -1
  46. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
  47. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  48. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +1 -1
  49. package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +0 -80
  50. package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +0 -140
  51. package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +0 -112
  52. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +0 -100
  53. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +0 -130
  54. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +0 -48
  55. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +0 -66
  56. package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +0 -103
  57. package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +0 -56
  58. 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
- /** List visible products (newest catalog order), mapped to grid-ready DTOs. */
137
- export async function fetchProducts({ limit = 100 } = {}): Promise<ProductSummary[]> {
138
- const res = await products
139
- .queryProducts({ fields: LIST_FIELDS as any })
140
- .limit(limit)
141
- .find();
142
- return (res.items ?? []).map((p) => toSummary(p as RawProduct));
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
- * List visible products in a category, server-side-filtered by the live category id.
147
- * Category filtering MUST go through searchProducts (the field is not filterable in
148
- * queryProducts) with the $matchItems operator — this is encoded here so callers never
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 = 100 } = {},
235
+ { limit = 24 } = {},
154
236
  ): Promise<ProductSummary[]> {
155
- const res: RawProduct = await products.searchProducts(
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
- : { physicalProperties: {}, inventoryItem: { quantity: quantity ?? 0, preorderInfo: { enabled: false } } }),
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
- if (!r.ok) throw new Error(`digitalFileUrl ${digitalFileUrl} -> ${r.status}`);
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
- const created = (r.productResults?.results ?? []).map((x, i) => ({
270
- id: x.item?.id, slug: x.item?.slug, revision: x.item?.revision,
271
- variantId: x.item?.variantsInfo?.variants?.[0]?.id,
272
- hasOptions: (products[i]?.options?.length ?? 0) > 0,
273
- isDigital: !!fileIds[i],
274
- quantity: products[i]?.quantity ?? 0,
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 created.map((p) => ({ id: p.id, slug: p.slug, revision: p.revision }));
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) => ({ productId: p.id, variantId: p.variantId, quantity: p.quantity }));
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 threaded in
347
- * memory. This is the default path — call it once instead of the individual functions.
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 ? await bulkCreateProducts(ctx, toCreate) : [];
358
- const createdByName = new Map(created.map((p, i) => [toCreate[i]?.name, p]));
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
- return { products: withNames, categories: cats, imagesAttached };
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
@@ -283,7 +286,7 @@ 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-ads/v1`.
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
  - **Offer a Success Guide after creating a PMAX Leads campaign (a proactive offer requires approval) / improve it / success guide / says they completed or fixed a guide recommendation / mark or reopen a recommendation** → [Manage a Campaign Success Guide](references/google-ads/manage-campaign-success-guide.md) (a direct improvement/guide request is already approval; works while `LEARNING` and before metrics exist).
@@ -293,25 +296,25 @@ Creates payment links for unpaid bookings using Payment Links API. Links booking
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-ads/v1."
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-ads/v1."
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-ads/v1."
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-ads/v1."
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
  "Retrieves and manages the campaign success guide for an existing Wix Google Ads Performance Max Leads campaign. Offer it after creating a supported campaign, even while it is learning or has no metrics; that proactive offer requires approval before retrieval. A direct request to improve a campaign, see what to fix next, or show the guide is already approval to retrieve it. Also use when a user reports completing or wants to reopen a guide item. Treat a clear completion report as an actionable update request even when phrased as a statement. Covers campaign selection, prioritized guide retrieval, and suggestion status updates without redundant confirmation. REST base https://www.wixapis.com/pa-platform/suggestions/v1."
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-ads/v1."
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-ads/v1."
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
+ Get a user's ready-made website live on Wix when they already have the site files — an index.html, a zip, or a static build exported from an AI builder or any other tool — by sending them to the Wix Headless drop page, where they upload the files and instantly get a live hosted site they can claim into their account. Use whenever the user wants to upload, publish, or host their own HTML/CSS/JS files as a NEW site. 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
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "Retrieve Google Ads Billing and Payment Details"
3
- description: "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-ads/v1."
3
+ description: "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."
4
4
  ---
5
5
  # RECIPE: Retrieve Google Ads Billing and Payment Details
6
6
 
7
7
  Report what a site owner will be charged for Google Ads: ad spend, the Wix service fee, coupon adjustments, the total, the billing period, and the account's credit balance.
8
8
 
9
- Base URL: `https://www.wixapis.com/google-ads/v1`. `<AUTH>` is the `Authorization` header. This endpoint is read-only.
9
+ Base URL: `https://www.wixapis.com/_serverless/pa-google/v1`. `<AUTH>` is the `Authorization` header. This endpoint is read-only.
10
10
 
11
11
  **Prerequisite:** a Google Ads account must exist (`ACCOUNT_NOT_FOUND` → run [install-and-create-account](install-and-create-account.md)).
12
12
 
@@ -17,7 +17,7 @@ Base URL: `https://www.wixapis.com/google-ads/v1`. `<AUTH>` is the `Authorizatio
17
17
  Billing is calculated from cached subscription data (not fetched live from Google), and the charge calculation is heavy — this endpoint has a **30-second SLA**, so allow time and don't retry prematurely.
18
18
 
19
19
  ```bash
20
- curl -X GET 'https://www.wixapis.com/google-ads/v1/payment-details' -H 'Authorization: <AUTH>'
20
+ curl -X GET 'https://www.wixapis.com/_serverless/pa-google/v1/payment-details' -H 'Authorization: <AUTH>'
21
21
  ```
22
22
 
23
23
  ```json
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "Create and Launch a Performance Max Campaign"
3
- description: "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-ads/v1."
3
+ description: "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."
4
4
  ---
5
5
  # RECIPE: Create and Launch a Performance Max Campaign
6
6
 
@@ -10,7 +10,7 @@ A **Performance Max (PMAX)** campaign runs across every Google channel from a si
10
10
  - **`PERFORMANCE_MAX_LEADS`** — lead-gen variant (phone/form conversions, search-theme signals, negative keywords, a ~28-day learning phase). **Requires** at least one asset group with headlines, descriptions, and images; assets are validated synchronously, so bad assets fail the create call immediately.
11
11
  - **Retail / Shopping** — a `PERFORMANCE_MAX` campaign linked to a Google Merchant Center feed (set `merchantCenterAccountId` on the account first — see [install-and-create-account](install-and-create-account.md) — and use `feedLabel`). Retail campaigns don't require a hand-built asset group.
12
12
 
13
- Base URL: `https://www.wixapis.com/google-ads/v1`. `<AUTH>` is the `Authorization` header; body calls also need `Content-Type: application/json`. The bidding strategy is server-enforced to `MAXIMIZE_CONVERSIONS` — never set it yourself.
13
+ Base URL: `https://www.wixapis.com/_serverless/pa-google/v1`. `<AUTH>` is the `Authorization` header; body calls also need `Content-Type: application/json`. The bidding strategy is server-enforced to `MAXIMIZE_CONVERSIONS` — never set it yourself.
14
14
 
15
15
  > **Launching spends real money.** Present the assembled asset group and budget and get explicit approval before the Launch call (STEP 5). Create in `PAUSED` first — nothing serves until Launch.
16
16
 
@@ -25,7 +25,7 @@ Base URL: `https://www.wixapis.com/google-ads/v1`. `<AUTH>` is the `Authorizatio
25
25
  `suggestionInfo.landingPageUrl` and `textSuggestionInfo.languageCode` are both required. Response may take up to 60s.
26
26
 
27
27
  ```bash
28
- curl -X POST 'https://www.wixapis.com/google-ads/v1/text-asset-suggestions' \
28
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/text-asset-suggestions' \
29
29
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
30
30
  -d '{
31
31
  "suggestionInfo": { "landingPageUrl": "https://www.example.com", "assetTypes": ["HEADLINE", "DESCRIPTION"] },
@@ -46,7 +46,7 @@ curl -X POST 'https://www.wixapis.com/google-ads/v1/text-asset-suggestions' \
46
46
  Images are generated with AI and **automatically uploaded to the site's Wix Media Manager** — the returned `url` is a `static.wixstatic.com` link you can use directly in the asset group. `suggestionInfo.landingPageUrl` is required.
47
47
 
48
48
  ```bash
49
- curl -X POST 'https://www.wixapis.com/google-ads/v1/image-asset-suggestions' \
49
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/image-asset-suggestions' \
50
50
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
51
51
  -d '{ "suggestionInfo": { "landingPageUrl": "https://www.example.com", "assetTypes": ["MARKETING_IMAGE", "SQUARE_MARKETING_IMAGE"] } }'
52
52
  ```
@@ -63,7 +63,7 @@ curl -X POST 'https://www.wixapis.com/google-ads/v1/image-asset-suggestions' \
63
63
  Search themes are the targeting signals for a leads campaign's asset group. `textSuggestionInfo.languageCode` is required.
64
64
 
65
65
  ```bash
66
- curl -X POST 'https://www.wixapis.com/google-ads/v1/search-theme-suggestions' \
66
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/search-theme-suggestions' \
67
67
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
68
68
  -d '{ "landingPageUrl": "https://www.example.com", "textSuggestionInfo": { "languageCode": "en" } }'
69
69
  ```
@@ -79,7 +79,7 @@ Each string becomes a `{ "searchTheme": { "text": "…" } }` signal in the asset
79
79
  PMAX uses **Generate Budget Recommendation** (not the Smart-campaign budget-suggestions endpoint). `campaignType`, `assetGroupInfo` (with a `finalUrl`), and `currency` are required.
80
80
 
81
81
  ```bash
82
- curl -X POST 'https://www.wixapis.com/google-ads/v1/budget-recommendation' \
82
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/budget-recommendation' \
83
83
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
84
84
  -d '{
85
85
  "campaignType": "PERFORMANCE_MAX",
@@ -129,7 +129,7 @@ Image `url` must be a Wix Media Manager URL (`static.wixstatic.com`) — the STE
129
129
  **Create a PMAX Leads campaign** (status required; create `PAUSED`):
130
130
 
131
131
  ```bash
132
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns' \
132
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns' \
133
133
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
134
134
  -d '{
135
135
  "campaign": {
@@ -178,7 +178,7 @@ Save the returned `campaign.id` and `resourceName`. `status` is read-only and sy
178
178
  ## STEP 5: Launch the campaign
179
179
 
180
180
  ```bash
181
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}/launch' \
181
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns/{campaignId}/launch' \
182
182
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' -d '{}'
183
183
  ```
184
184
 
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: "Get AI Campaign Suggestions for Google Ads"
3
- description: "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-ads/v1."
3
+ description: "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."
4
4
  ---
5
5
  # RECIPE: Get AI Campaign Suggestions for Google Ads
6
6
 
7
7
  The Suggestions API produces the values that make a campaign effective — keywords, locations, budgets, and creative assets — either from Google directly or from Wix's AI. It's the input layer for the create-campaign recipes; this file is the standalone reference for each endpoint and for the two that don't appear there (full campaign suggestions from a brief, and incentive offers).
8
8
 
9
- Base URL: `https://www.wixapis.com/google-ads/v1`. `<AUTH>` is the `Authorization` header; body calls also need `Content-Type: application/json`. All suggestion endpoints are **read-only** — none create or spend anything, so run them freely.
9
+ Base URL: `https://www.wixapis.com/_serverless/pa-google/v1`. `<AUTH>` is the `Authorization` header; body calls also need `Content-Type: application/json`. All suggestion endpoints are **read-only** — none create or spend anything, so run them freely.
10
10
 
11
11
  > **Two conventions to carry into every answer (this is where they trip people up):**
12
12
  > - **Budgets come back in micros.** Every `dailyBudget` / `budgetAmountMicros` / `recommendedBudgetAmountMicros` is in micros, where `1,000,000` micros = 1 unit of the account's currency (so `15000000` = $15.00/day). Always convert to currency units when presenting to a user, and pass micros back when creating a campaign.
@@ -39,7 +39,7 @@ Generates one or more complete, ready-to-use campaign configurations — assets,
39
39
  - `campaignType` — `SMART` (default) or `PERFORMANCE_MAX_LEADS`.
40
40
 
41
41
  ```bash
42
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaign-suggestions' \
42
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaign-suggestions' \
43
43
  -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
44
44
  -d '{ "campaignBriefId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "amount": 1, "campaignType": "SMART" }'
45
45
  ```
@@ -73,7 +73,7 @@ Present `campaignMessaging` and the config to the user, then use the `campaign`
73
73
  Credit offers for **new** accounts, granted after a spend threshold. Only supported currencies return offers. This drives the optional incentive step of [account setup](install-and-create-account.md).
74
74
 
75
75
  ```bash
76
- curl -X GET 'https://www.wixapis.com/google-ads/v1/incentives?currency=USD' -H 'Authorization: <AUTH>'
76
+ curl -X GET 'https://www.wixapis.com/_serverless/pa-google/v1/incentives?currency=USD' -H 'Authorization: <AUTH>'
77
77
  ```
78
78
 
79
79
  Returns `lowOffer` / `mediumOffer` / `highOffer`, each with `incentiveId`, `awardAmount`, and `requiredAmount`, plus a `consolidatedTermsAndConditionsUrl`. Pass the chosen `incentiveId` as `selectedIncentiveId` when creating the account.
@@ -15,7 +15,7 @@ Build direct links into the Google Ads page of a site's dashboard. For the gener
15
15
 
16
16
  ## Pairing Entities with Their Read APIs
17
17
 
18
- Fetch state via the Google Ads recipes in this area (`GET /google-ads/v1/accounts/current-site`, `GET /google-ads/v1/accounts/current-site/conversion-actions`), then link the page.
18
+ Fetch state via the Google Ads recipes in this area (`GET /_serverless/pa-google/v1/accounts/current-site`, `GET /_serverless/pa-google/v1/accounts/current-site/conversion-actions`), then link the page.
19
19
 
20
20
  Example — after creating a campaign:
21
21