@wix/agent-skills 1.19.2 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
  3. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
  4. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
  5. package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
  6. package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
  7. package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
  8. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
  9. package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
  10. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
  11. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
  12. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
  13. package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
  14. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
  15. package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
  16. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
  17. package/skills/wix-headless/references/CAPABILITIES.md +2 -2
  18. package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
  19. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
  20. package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
  21. package/skills/wix-headless-fast/install/fast-path.mjs +3 -1
  22. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +13 -0
  23. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +227 -56
  24. package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
  25. package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
  26. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +165 -0
  27. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +48 -15
  28. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +129 -11
  29. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +36 -6
  30. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +190 -24
  31. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
  32. package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
  33. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +5 -1
  34. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +59 -6
  35. package/skills/wix-manage/SKILL.md +4 -1
  36. package/skills/wix-manage/references/events/create-wix-event.md +46 -0
  37. package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
  38. package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
  39. package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
  40. package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
  41. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
  42. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
  43. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  44. package/skills/wix-vibe-headless/platforms/base44.md +19 -4
  45. package/skills/wix-vibe-headless/platforms/generic.md +6 -0
  46. package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
  47. package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
  48. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
  49. package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
  50. package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
  51. package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
  52. package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
  53. package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
  54. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
  55. package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
  56. package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
  57. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
  58. package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
  59. package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
  60. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
  61. package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
  62. package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
  63. package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
  64. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
  65. package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
  66. package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
  67. package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
  68. package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
  69. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
  70. package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
  71. package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
  72. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
  73. package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
  74. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +43 -2
  75. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
@@ -10,14 +10,24 @@ export interface ProductSummary {
10
10
  id: string;
11
11
  slug: string;
12
12
  name: string;
13
- /** Lowest price, formatted with currency symbol (e.g. "€34.99"). */
13
+ /**
14
+ * The price the buyer pays for the cheapest variant, formatted (an automatic discount already
15
+ * applied). When `price !== maxPrice` the product is a RANGE — render "price – maxPrice".
16
+ */
14
17
  price: string;
15
- /** Highest price, formatted — differs from `price` when variants are priced differently. */
18
+ /** Highest variant price, formatted — differs from `price` when variants are priced differently. */
16
19
  maxPrice: string;
17
- /** Strikethrough "was" price, formatted; null when not on sale. */
20
+ /**
21
+ * Struck "was" price, formatted; null when not on sale — and ALWAYS null for a range (a lone
22
+ * struck minimum beside a range claims a saving that may not apply to the variant picked).
23
+ */
18
24
  compareAtPrice: string | null;
19
- /** Merchant-set badge ("New", "Best Seller"); null when none. */
25
+ /** The primary merchant ribbon ("New", "Best Seller"); null when none. Same as ribbons[0]. */
20
26
  ribbon: string | null;
27
+ /** EVERY merchant ribbon, primary first ("New", "Sale") — render all of them, one shared style. */
28
+ ribbons: string[];
29
+ /** The cheapest variant's id — what a direct add sends for a product with no options; null when unknown. */
30
+ minPriceVariantId: string | null;
21
31
  availability: Availability;
22
32
  /** OUT_OF_STOCK but pre-orderable — label "Pre-order", not "Sold out". */
23
33
  preorder: boolean;
@@ -27,6 +37,11 @@ export interface ProductSummary {
27
37
  hoverImageUrl: string;
28
38
  /** e.g. "2 colors · 3 sizes"; "" for a single-variant product. */
29
39
  optionsSummary: string;
40
+ /**
41
+ * Hex colors of a color option's visible choices, catalog order — render as small dots on the
42
+ * tile (a preview, not a picker: selection happens in QuickAdd or on the PDP). [] when none.
43
+ */
44
+ swatches: string[];
30
45
  /** True when the product can be added to the cart with no choices (single variant, in stock). */
31
46
  quickAddable: boolean;
32
47
  }
@@ -60,15 +75,21 @@ export interface ProductVariant {
60
75
  variantId: string;
61
76
  /** The option selections this variant answers to: optionName -> choiceName. */
62
77
  choices: Record<string, string>;
78
+ /** The price the buyer pays, formatted — an automatic discount beats the regular price. */
63
79
  price: string;
80
+ /** Struck "was" price, formatted; null unless it is real and higher than `price`. */
64
81
  compareAtPrice: string | null;
65
82
  inStock: boolean;
83
+ /** Out of stock but pre-orderable — still buyable (the add carries preOrderRequested). */
84
+ preorderEnabled: boolean;
66
85
  }
67
86
 
68
87
  /** A product as the detail page needs it. */
69
88
  export interface ProductDetail extends ProductSummary {
70
89
  /** Product description as an HTML string — render with innerHTML, not as text. */
71
90
  descriptionHtml: string;
91
+ /** Merchant info sections (materials, shipping, care…) — title + HTML; render as sections or accordions. */
92
+ infoSections: { title: string; html: string }[];
72
93
  /** Every gallery image as a resolved https URL, main image first, de-duplicated. */
73
94
  gallery: string[];
74
95
  options: ProductOption[];
@@ -80,6 +101,36 @@ export interface Category {
80
101
  id: string;
81
102
  slug: string;
82
103
  name: string;
104
+ /** Plain-text description when the merchant wrote one; "" otherwise. */
105
+ description: string;
106
+ }
107
+
108
+ export interface FacetChoice {
109
+ /** The option choice id — what searchCatalog({ choiceIds }) filters on. */
110
+ id: string;
111
+ name: string;
112
+ /** Hex color for a swatch facet; null for a text facet. */
113
+ colorCode: string | null;
114
+ }
115
+
116
+ /** One filterable option across the catalog (or a category): "Color" with its choices. */
117
+ export interface Facet {
118
+ name: string;
119
+ isColor: boolean;
120
+ choices: FacetChoice[];
121
+ }
122
+
123
+ /** The catalog's (or category's) lowest and highest product price, as numbers in site currency — the slider's bounds. */
124
+ export interface PriceRange {
125
+ min: number;
126
+ max: number;
127
+ currency: string;
128
+ }
129
+
130
+ /** What the filter panel needs beyond the product page: the facets and the price bounds of the scope. */
131
+ export interface FacetData {
132
+ facets: Facet[];
133
+ priceRange: PriceRange | null;
83
134
  }
84
135
 
85
136
  export interface CartLine {
@@ -97,6 +148,11 @@ export interface CartLine {
97
148
  descriptionLines: string[];
98
149
  /** Not IN_STOCK → the line can't be checked out as-is. */
99
150
  status: string;
151
+ /**
152
+ * The recurring plan's terms for a subscription line — "Monthly plan · every month · 12 payments";
153
+ * "" for a one-time purchase. A subscription line must read as one in the cart.
154
+ */
155
+ subscription: string;
100
156
  }
101
157
 
102
158
  export interface Cart {
@@ -105,5 +161,7 @@ export interface Cart {
105
161
  itemCount: number;
106
162
  /** Formatted subtotal (after discounts) — from the cart estimate, not hand-summed. */
107
163
  subtotal: string;
164
+ /** Formatted CART-level discount (a coupon or cart rule) — "" when none; item discounts are already in subtotal. */
165
+ discount: string;
108
166
  currency: string;
109
167
  }
@@ -1,12 +1,18 @@
1
1
  ---
2
2
  // The site chrome for every storefront page — THIS file (header/footer/nav) plus the @theme
3
3
  // token block in src/styles/global.css are what you brand. Keep:
4
- // - the <slot name="seo-tags" /> in <head> (the product page's owner-editable SEO lands there)
4
+ // - the <slot name="seo-tags" /> in <head> (the product page's owner-editable SEO lands there,
5
+ // including the <title> — which is why ours is printed only when the slot is empty)
5
6
  // - the global.css import (Tailwind + the design tokens every shipped component reads)
6
7
  // - one <CartButton> in the header and one <CartDrawer> per page (both client:only —
7
8
  // they read browser cart state and must not server-render)
9
+ // - category links in the nav (or your own menu/footer) — every category page reachable by
10
+ // a real link (`/category/<slug>`), from the live tree, so a category the owner adds later
11
+ // shows up with no code change
8
12
  import CartButton from "../components/storefront/CartButton";
9
13
  import CartDrawer from "../components/storefront/CartDrawer";
14
+ import { fetchCategories } from "../wix/storefront/catalog";
15
+ import type { Category } from "../wix/storefront/types";
10
16
  import "../styles/global.css";
11
17
 
12
18
  interface Props {
@@ -14,6 +20,15 @@ interface Props {
14
20
  description?: string;
15
21
  }
16
22
  const { title, description } = Astro.props;
23
+
24
+ // Non-fatal: the chrome renders without the links if the read fails. Six in the header keeps a
25
+ // large tree from crowding it — put the rest in a menu or the footer, but keep them linked.
26
+ let navCategories: Category[] = [];
27
+ try {
28
+ navCategories = (await fetchCategories()).slice(0, 6);
29
+ } catch {
30
+ /* the shop link still reaches everything */
31
+ }
17
32
  ---
18
33
 
19
34
  <!doctype html>
@@ -21,7 +36,8 @@ const { title, description } = Astro.props;
21
36
  <head>
22
37
  <meta charset="UTF-8" />
23
38
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
24
- <title>{title}</title>
39
+ {/* The product page's <SEO.Tags> renders the <title>; print ours only when no page filled the slot — two titles otherwise. */}
40
+ {!Astro.slots.has("seo-tags") && <title>{title}</title>}
25
41
  {description && <meta name="description" content={description} />}
26
42
  <slot name="seo-tags" />
27
43
  </head>
@@ -31,6 +47,9 @@ const { title, description } = Astro.props;
31
47
  <a href="/" class="text-base font-bold tracking-tight text-foreground no-underline">Brand Name</a>
32
48
  <nav class="flex items-center gap-6 text-sm">
33
49
  <a href="/shop" class="text-foreground no-underline transition-colors hover:text-muted-foreground">Shop</a>
50
+ {navCategories.map((c) => (
51
+ <a href={`/category/${c.slug}`} class="hidden text-foreground no-underline transition-colors hover:text-muted-foreground md:inline">{c.name}</a>
52
+ ))}
34
53
  <CartButton client:only="react" />
35
54
  </nav>
36
55
  </div>
@@ -75,7 +75,11 @@ the shipped UI**: give at least one product a color option and put one product o
75
75
  truthfully to the business (a ceramics studio has glaze colors; a bakery doesn't).
76
76
 
77
77
  **Seeding is additive — never delete or overwrite existing content.** No cleanup, no removing
78
- "sample" data, no resets. If a cleanup genuinely seems needed, ask the user first.
78
+ "sample" data, no resets. If a cleanup genuinely seems needed, ask the user first. The one
79
+ exception is built in: the fast path passes `--fresh-site` because it created the site seconds
80
+ earlier, and the seed then deletes the Stores install's sample products before creating yours
81
+ (`samplesDeleted` in the result). Never pass that flag by hand against a site that existed before.
82
+ Categories are idempotent by name too — a re-run reuses "Donuts" instead of creating a second one.
79
83
 
80
84
  **A bulk create can partially succeed.** The result carries `failures: [{ name, error }]` next
81
85
  to `products` — read it. A non-empty `failures` means those products are genuinely absent, not
@@ -1,7 +1,7 @@
1
1
  // Storefront seed — a BUILD-TIME script, never shipped in the app. Run it from the project
2
2
  // root (where wix.config.json lives) with a plan file:
3
3
  //
4
- // node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json
4
+ // node <SKILL_ROOT>/references/storefront/seed/seed-store.mjs plan.json [--fresh-site]
5
5
  //
6
6
  // It mints its own site token via the Wix CLI (the token never leaves this process), installs
7
7
  // the Wix Stores app if needed, waits for the V3 catalog, bulk-creates products (variants
@@ -331,11 +331,34 @@ async function stockOptionlessProducts(ctx, created) {
331
331
  }
332
332
  }
333
333
 
334
+ // Existing categories by name (for idempotent reruns) — a re-run of the seed must reuse
335
+ // "Donuts", not create a second one. Empty map on any failure (falls back to create).
336
+ export async function queryCategoriesByNames(ctx, names) {
337
+ const out = new Map();
338
+ if (!names.length) return out;
339
+ try {
340
+ const r = await req(ctx, "/categories/v1/categories/query", {
341
+ body: { treeReference: { appNamespace: "@wix/stores", treeKey: null }, query: { cursorPaging: { limit: 100 } } },
342
+ });
343
+ const wanted = new Set(names);
344
+ for (const c of r.categories ?? []) if (wanted.has(c.name) && !out.has(c.name)) out.set(c.name, c.id);
345
+ } catch (e) {
346
+ console.error(`category name pre-check failed (creating everything): ${String(e.message).slice(0, 120)}`);
347
+ }
348
+ return out;
349
+ }
350
+
334
351
  // Categories share the @wix/stores tree revision — concurrent creates 409, so: sequential.
352
+ // Idempotent by name: a name that already exists is reused, never duplicated.
335
353
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/create-category.md
336
354
  export async function createCategories(ctx, names) {
355
+ const existing = await queryCategoriesByNames(ctx, names);
337
356
  const out = [];
338
357
  for (const name of names) {
358
+ if (existing.has(name)) {
359
+ out.push({ id: existing.get(name), name });
360
+ continue;
361
+ }
339
362
  const r = await req(ctx, "/categories/v1/categories", {
340
363
  body: { category: { name, visible: true }, treeReference: { appNamespace: "@wix/stores", treeKey: null } },
341
364
  });
@@ -344,6 +367,24 @@ export async function createCategories(ctx, names) {
344
367
  return out;
345
368
  }
346
369
 
370
+ // The Stores install drops sample products ("I'm a product") into every new catalog. On a site the
371
+ // fast path JUST created nothing else can exist, so they are deleted before seeding — otherwise the
372
+ // storefront ships with the samples between the real products. Only called with --fresh-site; a
373
+ // seed against an existing site never deletes anything (SEED.md: seeding is additive).
374
+ // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/bulk-delete-products.md
375
+ export async function deleteAllProducts(ctx) {
376
+ let deleted = 0;
377
+ for (let pass = 0; pass < 10; pass++) {
378
+ const r = await req(ctx, "/stores/v3/products/query", { body: { query: { cursorPaging: { limit: 100 } } } });
379
+ const ids = (r.products ?? []).map((p) => p.id).filter(Boolean);
380
+ if (!ids.length) break;
381
+ await req(ctx, "/stores/v3/bulk/products/delete", { body: { productIds: ids } });
382
+ deleted += ids.length;
383
+ if (ids.length < 100) break;
384
+ }
385
+ return deleted;
386
+ }
387
+
347
388
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/bulk-add-items-to-category.md
348
389
  export async function addProductsToCategories(ctx, mapping) {
349
390
  for (const [categoryId, productIds] of Object.entries(mapping)) {
@@ -421,12 +462,22 @@ async function setSiteCurrency(ctx, currency) {
421
462
  * threaded in memory. This is the default path — call it once instead of the individual
422
463
  * functions.
423
464
  */
424
- export async function setupStore(ctx, { products = [], categories = {}, currency } = {}) {
465
+ export async function setupStore(ctx, { products = [], categories = {}, currency } = {}, { freshSite = false } = {}) {
425
466
  validateProducts(products);
426
467
  await installStoresApp(ctx);
427
468
  // Before any product exists: a product's price is stored in the site currency at create time,
428
469
  // so switching afterwards leaves the catalog priced in the old one.
429
470
  if (currency) await setSiteCurrency(ctx, currency);
471
+ // A just-created site holds only the install's sample products — clear them (never on an
472
+ // existing site: the flag is the fast path's, set only when it created the site itself).
473
+ let samplesDeleted = 0;
474
+ if (freshSite) {
475
+ try {
476
+ samplesDeleted = await deleteAllProducts(ctx);
477
+ } catch (e) {
478
+ console.error(`sample cleanup failed (seeding on top): ${String(e.message).slice(0, 120)}`);
479
+ }
480
+ }
430
481
 
431
482
  // Idempotent by name: an errored bulk create (429/5xx) may still have applied server-side,
432
483
  // and SKILL.md tells the agent to re-run a failed seed — creating only the names that don't
@@ -478,21 +529,23 @@ export async function setupStore(ctx, { products = [], categories = {}, currency
478
529
  // failures is part of the result, not an exception: a partial seed still leaves a usable
479
530
  // store, and the agent needs the names to report rather than silently shipping a short
480
531
  // catalog. Re-run the seed to retry them — existing names are skipped, not duplicated.
481
- return { products: withNames, categories: cats, imagesAttached, failures };
532
+ return { products: withNames, categories: cats, imagesAttached, samplesDeleted, failures };
482
533
  }
483
534
 
484
535
  // ---- CLI entry ----------------------------------------------------------------------------------
485
536
 
486
537
  const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split("/").pop());
487
538
  if (invokedDirectly) {
488
- const planPath = process.argv[2];
539
+ const args = process.argv.slice(2);
540
+ const freshSite = args.includes("--fresh-site");
541
+ const planPath = args.find((a) => !a.startsWith("--"));
489
542
  if (!planPath) {
490
- console.error("usage: node seed-store.mjs <plan.json> (run from the project root)");
543
+ console.error("usage: node seed-store.mjs <plan.json> [--fresh-site] (run from the project root)");
491
544
  process.exit(1);
492
545
  }
493
546
  const plan = JSON.parse(readFileSync(planPath, "utf8"));
494
547
  const ctx = makeCtx();
495
- setupStore(ctx, plan)
548
+ setupStore(ctx, plan, { freshSite })
496
549
  .then((result) => console.log(JSON.stringify(result, null, 2)))
497
550
  .catch((e) => {
498
551
  console.error(e.message);
@@ -48,6 +48,9 @@ Generate an SEO content plan and read its blog post topics, or troubleshoot an e
48
48
  ### [Manage Google Search Console for a Wix Site](references/seo/manage-google-search-console.md)
49
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
50
 
51
+ ### [Generate AI SEO Suggestions for a Wix Site](references/seo/manage-seo-suggestions.md)
52
+ "Generate AI-written SEO text for a Wix site through the public Tag Suggestions and Page Optimization APIs: title tag and meta description options for a page, alt text for the images of a page, store product, or blog post (one image or up to 20 per call), corrected heading levels for a page's headings, and a whole-page rewrite of title, description, headings, and body text aimed at the page's focus keyword, returned as before/after pairs. Use it when the user wants to improve, optimize, audit, or write the SEO text of a page or the homepage. Suggestions are returned for review; applying one is a separate SEO tags write."
53
+
51
54
  ---
52
55
 
53
56
  ## Accessibility
@@ -251,7 +254,7 @@ Buy a domain through Wix or connect one the user already owns — intent, availa
251
254
  "Creates an event with the Wix Events V3 API — the required request body, ISO-8601 date and time settings, venue/online/TBD location, RSVP vs ticketed registration, guest capacity, short vs rich-text descriptions, ticket tiers and pricing, and recurring series. Covers the exact field shapes and the API's misleading validation messages. Use when the user wants to create an event, set its date, location, description, guest limit or ticket prices, or set up a repeating event."
252
255
 
253
256
  ### [Manage Wix Events — Publishing, Cancelling, Cloning and Counting](references/events/manage-wix-events.md)
254
- "Operates on events that already exist with the Wix Events V3 API — publishing a draft, cancelling, deleting, cloning, updating an event's date or details, and counting events. Use when the user wants to publish or cancel an event, duplicate one, move an event's date, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
257
+ "Operates on events that already exist with the Wix Events V3 API — finding an event by title, publishing a draft, cancelling, deleting (one or by filter), cloning, updating an event's date or details, and counting events. Use when the user wants to find, publish or cancel an event, duplicate one, move an event's date, delete events in bulk, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
255
258
 
256
259
  ---
257
260
 
@@ -56,6 +56,32 @@ curl -X POST 'https://www.wixapis.com/events/v3/events' \
56
56
  `title`, `location`, `dateAndTimeSettings` and `registration.initialType` are required. The event
57
57
  is created published (`status: "UPCOMING"`).
58
58
 
59
+ **What comes back** — the created event under `event`; the id you need for every follow-up call is
60
+ `event.id`:
61
+
62
+ ```json
63
+ {
64
+ "event": {
65
+ "id": "<EVENT_ID>",
66
+ "title": "Summer Gala",
67
+ "slug": "summer-gala",
68
+ "status": "UPCOMING",
69
+ "location": { "name": "Grand Hall", "type": "VENUE", "locationTbd": false },
70
+ "dateAndTimeSettings": {
71
+ "startDate": "2026-09-15T19:00:00Z", "endDate": "2026-09-15T22:00:00Z",
72
+ "timeZoneId": "America/New_York", "recurrenceStatus": "ONE_TIME"
73
+ },
74
+ "createdDate": "...", "updatedDate": "..."
75
+ }
76
+ }
77
+ ```
78
+
79
+ Update, cancel, publish and clone return the same `{ "event": { ... } }`; delete returns
80
+ `{ "eventId": "..." }`. Some fields are returned only when the request's `fields` array asks for
81
+ them: `"DETAILS"` adds `shortDescription`, `"TEXTS"` adds `description`, `"REGISTRATION"` adds
82
+ `registration` (including `rsvp.limit`), `"URLS"` adds `eventPageUrl`. Without `fields`, read the
83
+ result from the top-level fields above and do not treat a missing `registration` as an error.
84
+
59
85
  > **Do not add `"draft": true` unless the user asked for a draft.** Draft events require the
60
86
  > `WIX_EVENTS.READ_DRAFT_EVENTS` permission, and without it every follow-up call fails `403` —
61
87
  > adding ticket definitions, querying the event, fetching it by slug, even publishing it. The
@@ -154,6 +180,26 @@ curl -X POST 'https://www.wixapis.com/events/v3/ticket-definitions' \
154
180
  | Free | `{ "fixedPrice": { "value": "0", "currency": "USD" } }` |
155
181
  | Donation, with a minimum | `{ "guestPrice": { "value": "5.00", "currency": "USD" } }` |
156
182
 
183
+ The response is the definition under `ticketDefinition`:
184
+
185
+ ```json
186
+ {
187
+ "ticketDefinition": {
188
+ "id": "<TICKET_DEFINITION_ID>",
189
+ "eventId": "<EVENT_ID>",
190
+ "revision": "1",
191
+ "name": "General Admission",
192
+ "limited": true, "initialLimit": 100, "actualLimit": 100,
193
+ "pricingMethod": { "fixedPrice": { "value": "25.00", "currency": "USD" } },
194
+ "feeType": "FEE_ADDED_AT_CHECKOUT",
195
+ "saleStatus": "SALE_STARTED"
196
+ }
197
+ }
198
+ ```
199
+
200
+ Keep `revision` — `PATCH /events/v3/ticket-definitions/{id}` requires the current value, and it
201
+ increments on every update.
202
+
157
203
  - **`feeType` is required** — `FEE_ADDED_AT_CHECKOUT` or `FEE_INCLUDED`.
158
204
  - **`value` is a string.** `"value": 10` fails `400 Unexpected value for field value`.
159
205
  - **No writable `free` flag** — `pricingMethod.free` is read-only; a free ticket is a `fixedPrice`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: "Manage Wix Events — Publishing, Cancelling, Cloning and Counting"
3
- description: "Operates on events that already exist with the Wix Events V3 API — publishing a draft, cancelling, deleting, cloning, updating an event's date or details, and counting events. Use when the user wants to publish or cancel an event, duplicate one, move an event's date, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
3
+ description: "Operates on events that already exist with the Wix Events V3 API — finding an event by title, publishing a draft, cancelling, deleting (one or by filter), cloning, updating an event's date or details, and counting events. Use when the user wants to find, publish or cancel an event, duplicate one, move an event's date, delete events in bulk, or count their events. Creating an event, its tickets or a recurring series is a separate recipe."
4
4
  ---
5
5
 
6
6
  # Manage Wix Events — Publishing, Cancelling, Cloning and Counting
@@ -28,6 +28,36 @@ curl -X POST 'https://www.wixapis.com/apps-installer-service/v1/app-instance/ins
28
28
  `appDefId` nests under `appInstance`, not at the root — at the root it fails with
29
29
  `400 appInstance must not be empty`. See [Install Wix Apps](../app-installation/install-wix-apps.md).
30
30
 
31
+ ## Find an event
32
+
33
+ Every action below takes an `eventId`. When the user names the event instead, look it up with
34
+ `POST /events/v3/events/query` — `title` supports `$eq` and, for several names at once, `$in`; there is
35
+ no contains match:
36
+
37
+ ```bash
38
+ curl -X POST 'https://www.wixapis.com/events/v3/events/query' \
39
+ -H 'Content-Type: application/json' \
40
+ -H 'Authorization: <AUTH>' \
41
+ --data-binary '{
42
+ "query": {
43
+ "filter": { "title": { "$eq": "Open House" } },
44
+ "paging": { "limit": 100 }
45
+ },
46
+ "fields": ["DETAILS"],
47
+ "includeDrafts": false
48
+ }'
49
+ ```
50
+
51
+ `filter` and `paging` are siblings under `query`; `paging` nested inside `filter` fails `400`. Matches
52
+ come back in `events[]` with the id at `events[].id`, and `pagingMetadata.total` is the count.
53
+
54
+ > **Keep `includeDrafts: false` unless the user is asking about draft events.** Setting it to `true`
55
+ > needs `WIX_EVENTS.READ_DRAFT_EVENTS`, and a caller without it gets
56
+ > `403 Requires WIX_EVENTS.READ_DRAFT_EVENTS permission` rather than more results — so do not flip
57
+ > it as a precaution on an ordinary lookup, and do not probe for it and fall back. If the user does
58
+ > want drafts and the call returns that `403`, say draft events are not readable from this
59
+ > connection instead of answering from published events alone.
60
+
31
61
  ## Publish, cancel and delete
32
62
 
33
63
  | Action | Call | Resulting `status` |
@@ -35,10 +65,27 @@ curl -X POST 'https://www.wixapis.com/apps-installer-service/v1/app-instance/ins
35
65
  | Publish a draft | `POST /events/v3/events/{eventId}/publish` | `UPCOMING` |
36
66
  | Cancel | `POST /events/v3/events/{eventId}/cancel` | `CANCELED` |
37
67
  | Delete | `DELETE /events/v3/events/{eventId}` | — |
68
+ | Delete several | `POST /events/v3/bulk/events/delete-by-filter` | — |
38
69
 
39
70
  Publish and cancel take an empty body. Publishing is irreversible — a published event cannot
40
71
  return to `DRAFT`. Cancelling closes registration but keeps the event; deleting removes it.
41
72
 
73
+ Publish, cancel, clone and update all return `{ "event": { "id", "title", "slug", "status", ... } }`
74
+ — the same event object [Create an Event](create-wix-event.md#create-the-event) shows in full.
75
+ Delete returns `{ "eventId": "..." }` and the bulk delete below returns `{}`.
76
+
77
+ To delete a set of events in one call, `POST /events/v3/bulk/events/delete-by-filter`:
78
+
79
+ ```json
80
+ { "filter": { "filter": { "id": { "$in": ["<EVENT_ID>", "<EVENT_ID>"] } } } }
81
+ ```
82
+
83
+ The request's one field, `filter`, takes a whole query-shaped object — the same object Query Events
84
+ takes under `query`, with the conditions under its own `filter` key — not a bare condition map. That
85
+ is why the key appears twice: `filter.filter.<field>`. A single level,
86
+ `{ "filter": { "id": { "$in": [...] } } }`, is not the documented shape. Resolve the ids with the
87
+ query above first; one `DELETE` per event also works but costs a call each.
88
+
42
89
  > **Draft events need the `WIX_EVENTS.READ_DRAFT_EVENTS` permission.** Without it, publishing a
43
90
  > draft fails `403` — as does querying it, fetching it by slug, or adding ticket definitions to
44
91
  > it. If you hit that `403`, the event was still created; the way forward is to create events
@@ -104,6 +151,9 @@ even when the site has events, so it is not the way to answer "how many events d
104
151
  - **An invalid enum value reports as a missing one** — a value outside an enum returns
105
152
  `<field> value is required` rather than "invalid value". If a field you *did* send is reported
106
153
  as required, suspect the value, not its presence.
154
+ - **`includeDrafts` stays `false` unless the request is about drafts** — see
155
+ [Find an event](#find-an-event). The `WIX_EVENTS.READ_DRAFT_EVENTS` note on the Query Events
156
+ reference describes the permission; it does not mean the caller has it.
107
157
  - Dates are always ISO-8601 strings, never `{seconds, nanos}`, and `timeZoneId` is required
108
158
  whenever `dateAndTimeSettings` is sent — see [Create an Event](create-wix-event.md).
109
159
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: "Manage Campaign Lifecycle"
3
- description: "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."
3
+ description: "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."
4
4
  ---
5
5
  # RECIPE: Manage Campaign Lifecycle
6
6
 
7
- Operate on campaigns that already exist. Base URL: `https://www.wixapis.com/google-ads/v1`; `<AUTH>` = `Authorization` header, and body calls also need `Content-Type: application/json`.
7
+ Operate on campaigns that already exist. Base URL: `https://www.wixapis.com/_serverless/pa-google/v1`; `<AUTH>` = `Authorization` header, and body calls also need `Content-Type: application/json`.
8
8
 
9
9
  **Answer directly.** When the user asks *how* to do something, give the endpoint, an example request, and the key behavior right away — use `{campaignId}` as a placeholder rather than asking which campaign. Only pause to confirm when you are about to **execute** a launch, resume, or delete on the user's behalf (those spend money or are irreversible); explaining how never requires confirmation.
10
10
 
@@ -21,14 +21,14 @@ Operate on campaigns that already exist. Base URL: `https://www.wixapis.com/goog
21
21
 
22
22
  ```bash
23
23
  # Launch (first activation) → status LIVE
24
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}/launch' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' -d '{}'
24
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns/{campaignId}/launch' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' -d '{}'
25
25
 
26
26
  # Pause a running campaign (settings + data preserved) → status PAUSED
27
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}/pause' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
27
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns/{campaignId}/pause' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
28
28
  -d '{ "scheduledResumeDate": "2024-04-15T08:00:00.000Z" }'
29
29
 
30
30
  # Resume a paused campaign → status LIVE
31
- curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}/resume' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' -d '{}'
31
+ curl -X POST 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns/{campaignId}/resume' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' -d '{}'
32
32
  ```
33
33
 
34
34
  - **Pause** stops delivery immediately without losing configuration. Optional body fields: `scheduledResumeDate` (ISO 8601 — the campaign **auto-resumes** at that time; pass `null` to cancel a scheduled resume), `resumeReminderDate` (dashboard reminder), `turnAutoRenewOff`. Send `{}` to pause with no auto-resume. To temporarily stop a campaign, **pause it — never delete**.
@@ -39,7 +39,7 @@ curl -X POST 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}/resum
39
39
  `PATCH /v1/campaigns/{id}` — send only the fields you're changing, plus the required `id` and `accountId`. Budget is in **micros** (`20000000` = $20.00/day).
40
40
 
41
41
  ```bash
42
- curl -X PATCH 'https://www.wixapis.com/google-ads/v1/campaigns/{campaignId}' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
42
+ curl -X PATCH 'https://www.wixapis.com/_serverless/pa-google/v1/campaigns/{campaignId}' -H 'Authorization: <AUTH>' -H 'Content-Type: application/json' \
43
43
  -d '{ "campaign": { "id": "...", "accountId": "...", "name": "New name", "budget": { "amountMicros": "20000000" } } }'
44
44
  ```
45
45