Rp Target Wix
WixOfficialPublished by Wix
- Skill
- Tool integration
- Software development
- MIT
- Medium risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The rp-target-wix skill serves as the authoritative Wix-side write surface for migration projects. It centralizes verified request builders, shared execution logic, and contract testing to ensure consistency across different source platforms. By decoupling the Wix API plumbing from project-specific migration logic, it prevents redundant implementation and reduces the risk of breaking changes. The skill manages critical runtime behaviors such as retries, throttling, checkpointing, and audit logging, while providing a structured approach to domain entity suitability and dependency management.
What it does
- Provides verified request builders and execution primitives for Wix API interactions.
- Implements shared runtime logic for retries, throttling, and checkpointing.
- Manages crosswalk helpers and URL preservation for data integrity.
- Defines domain entity suitability and dependency wiring via structured domain files.
- Includes contract tests to ensure stable request and runtime behavior.
- Supports bulk operations and normalization for efficient data ingestion.
When to use it
- When building or maintaining migration pipelines that target Wix as the destination platform.
- When you need to perform verified writes to Wix entities like products, orders, or contacts.
- When implementing shared migration infrastructure that requires consistent throttling and retry policies.
- When validating API shapes or provisioning mechanics against live Wix environments.
Advantages
- Reduces code duplication by centralizing Wix API plumbing and runtime logic.
- Ensures high reliability through verified request builders and contract tests.
- Simplifies the addition of new source platforms by maintaining a platform-agnostic write surface.
- Provides robust error handling and rate-limiting strategies out of the box.
- Facilitates auditability through standardized execution reports and logging.
Usage examples
- “How do I ensure my migration handles Wix Stores product bulk creation correctly?”
- The skill provides a bulk creation primitive that handles normalization and correlation. You should use the exported BULK_PRODUCT_LIMITS and storesProductBulkCost to batch records via the ndjson stream, ensuring you correlate results by itemMetadata.originalIndex rather than HTTP status.
- “How should I handle Wix site notifications during a large import?”
- Use the site notifications mute primitives to mute all site notifications during the import window. Ensure you pass a project-identifying reason string to the mute call for auditability and use the idempotent re-mute pattern during preflight checks.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/wix/rp-target-wix-nymy/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip rp-target-wix.zip -d .agents/skills
Documentation by Wix
rp-target-wix
Wix target adapter. Owns the Wix-side write surface and shared execution runtime that every migration shares —
verified once here so the platform-agnostic stages and per-project codegen never
re-derive (and re-break) it. This is the symmetric counterpart to rp-source-wordpress:
that adapter owns reading a source platform; this one owns writing to Wix.
When this skill is used
Not a stage in the flow — a reference + shared library consulted by:
rp-import-codegenvendorslib/wix-writers.jsinto the project (like thewp-httptransport) plus the shared write/setup runtime modules, and generates thin project-specific write specs/transforms that call these primitives/runtime APIs. Codegen does not re-emit Wix API plumbing.rp-setup-discovery/rp-execute-setupconsult the "Verified endpoints" and "Provisioning" notes for app-install / Wix-Data / collection mechanics.
Because the Wix surface is identical across source platforms, adding a new source
(rp-source-shopify, …) requires no change here.
Shared runtime boundary
This adapter owns the shared Wix layer written once for all migrations.
It should own:
verified request builders and endpoint primitives
shared Wix client/auth handling
shared retry and throttling policy
generic write execution runtime
generic setup execution/runtime behavior
checkpoint helpers
local crosswalk helpers (
state/crosswalk/crosswalk.ndjsonauthority, append-only attempt journal, optional CMS mirror seed/upload adapters)URL preservation helpers (
state/url-preservation/base paths, ledger, planned redirects, and unresolved route rows)NDJSON record-stream I/O (
lib/ndjson.js): streaming read/write, batching by count and by cost, cursoring, and append-only producers. Migration data files are record streams, so the scan/batch/cursor primitives live here once instead of being re-derived by codegen.audit log emission
compact execution report generation
contract tests for stable request/runtime behavior
Per-migration code generation should own only:
field maps
transforms
write specs
setup-plan renderings
entity ordering
dependency wiring — populate
WriteSpec.dependencyRefsfrom each in-scope entity's domain-knowledgedependsOnfield (spec 0041), not re-derived per project
Write contract (the verified Wix surface)
lib/wix-writers.js exposes pure request builders (build*Request) + executors. Shapes
marked VERIFIED were validated by a real call against a live site, not just read from
docs. Shapes marked UNVERIFIED are docs-schema/MCP-derived bootstrap primitives that
must be surfaced in execution plans until a live contract call promotes them. It also
exports sendDirectRest and notifyMissingWriter for generated native REST paths when
Wix has a native entity but this adapter does not yet ship a dedicated primitive.
This resource should also expose shared execution-runtime modules above those primitives, so generated migrations use one common path for retries, throttling, checkpointing, reporting, and audit logging.
Read/return contract for query* executors. Every query* executor
(queryStoresProducts, queryStoresCategories, queryContacts, queryCoupons,
queryOrders) unwraps the response to the entity array and returns one page, discarding
pagingMetadata. Two failure modes follow, both of which have shipped:
The return value IS the array. Reading
.products/.categoriesoff it a second time givesundefined→[]. A dedupe index or existing-entity safety net then comes back empty without erroring, which is indistinguishable from a fresh site — so the import duplicates what is already there, and a setup verification reports 0 categories on a site with 25.These executors cannot be cursor-paged, because the cursor is in the metadata they discarded. Use a
queryAll*primitive where one exists —queryAllStoresCategories,queryAllStoresProducts,queryAllDataItems— otherwise sendwix.send(build<X>Request(body))and readpagingMetadata.cursors.nextoff the raw response.
Every sweep used for dedupe must throw on a partial result rather than return it. "Empty net" and "empty store" look identical to the caller, and the caller assumes the second.
Domain entity suitability knowledge
Target entity suitability lives in domains/, not in a large prose table in this skill.
Use the deterministic reader as the normal access path:
node scripts/domain-knowledge.js list-domains
node scripts/domain-knowledge.js list-entities --domain stores
node scripts/domain-knowledge.js read-entity --ref stores/product
node scripts/domain-knowledge.js resolve-source --source-system woocommerce --source-entity product
node scripts/domain-knowledge.js resolve-source --route /wc/v3/products
node scripts/domain-knowledge.js list-flagged --flag IMPORT_UNRELIABLE
node scripts/domain-knowledge.js summarize-entities --refs stores/product,ecom/orderdomains/index.json is generated from domain/entity files and checked in. After editing
domain knowledge, run:
node scripts/domain-knowledge-validate.js --write-indexAuthoring or editing any entity file also requires the spec 0041 dependency check, both
directions: (1) what does this entity need to already exist and be crosswalked — record it in
dependsOn: string[] (a domain/entity ref array; empty is a real, reviewed answer, not the same
as the field being absent); (2) does this entity change what an already-written entity's
dependsOn should say (e.g. authoring gift-cards/gift-card should have prompted revisiting
ecom/order.json — it hadn't, until spec 0042 caught it by hand). domain-knowledge-validate.js
checks dependsOn refs resolve and the whole graph stays acyclic whenever the field is present, but
it is not yet required on every entity — node scripts/domain-knowledge-validate.js --list-missing-deps prints the live backlog of entities that have never had it authored at all;
that command is the source of truth, not a hand-maintained list.
writerId values in entity files must match exported functions from lib/wix-writers.js;
use null for direct REST plans, setup/manual work, or unsupported native gaps.
Entity files may also expose fieldContracts[] for verified nested write paths. These
are machine-readable contracts for codegen and dry-run validators, not replacements for
the human notes below. For Stores products, domains/stores/entities/product.json
records the verified Catalog V3 create contract for product.subscriptionDetails,
including required nested paths, allowed recurrence values, read-back behavior, and the
live-validated subscriptions[].description <= 60 constraint. Generated transforms
should consume that metadata through the vendored wix-writers.js exports instead of
copying limits into project-local code.
Live verification helpers must not edit domains/ directly. When a probe changes a
target write assumption, emit a proposal artifact and let the orchestrator promote it
deliberately:
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
--artifact migrations/<project>/setup/stores-subscription-verification.json \
--proposal-artifact migrations/<project>/setup/contract-ledger-proposal.jsonUse lib/contract-ledger.js to validate the handoff: a passed verification without a
proposal is incomplete, and a proposal that is neither promoted into fieldContracts[]
nor explicitly deferred is stale product knowledge. Codegen should consume
fieldContracts[] through scripts/domain-knowledge.js summarize-entities, which
includes promoted contracts.
| Capability | Endpoint | Notes / traps |
|---|---|---|
| HTML → rich content | POST /ricos/v1/ricos-document/convert/to-ricos | VERIFIED. options.plugins enum is UPPERCASE — the docs example shows lowercase and 400s. |
| Import media from URL | POST /site-media/v1/files/import | VERIFIED. Async: response is PENDING; poll GET /site-media/v1/files/{id} for READY before referencing. |
| Blog category | POST /blog/v3/categories | VERIFIED. body { category: { label, slug, description } }. |
| Blog tag | POST /blog/v3/tags | VERIFIED. Body is top-level { label, language } — NOT { tag: { label, slug } }; slug is derived. |
| Blog post | POST /blog/v3/draft-posts → …/{id}/publish | VERIFIED. memberId required (3rd-party). VERIFIED (2026-08-02): the site owner's auto-created user-member satisfies it — it was already present on our API-provisioned test site with zero Members-area interaction (single-site observation), so attribute-to-owner needs no member provisioning. Resolve it via listMembers + loginEmail match — never derive it from the account/user GUID (the observed id equality is n=1 on a solo account, undocumented). Also VERIFIED (2026-08-02): author is re-assignable after publish — PATCH /blog/v3/draft-posts/{id} { draftPost: { memberId } } then republish updates the published post's author (post id == draft id; republish fires non-suppressed events — do author-upgrade passes inside the notification-mute window). Visible custom cover media requires both heroImage.id and media: { displayed:true, custom:true, wixMedia:{ image:{ id }}}. heroImage.id alone leaves the cover hidden. |
| Blog posts (bulk) | POST /blog/v3/bulk/draft-posts/create | UNVERIFIED bootstrap (public docs + wix/skills wix-manage recipe, 2026-07-21). Max 20 posts/call; bulkCreateDraftPosts chunks larger inputs. Prefer over per-post creates for N ≥ 2 (single create runs ~25–30s/call per the recipe). Publish is still per-post — a bulk publish flag is unverified. Surface in the execution plan until the live contract call promotes it. |
| CMS item | POST /wix-data/v2/items | VERIFIED. { dataCollectionId, dataItem: { data } }. Requires Wix Data enabled (else WDE0110). |
| Members | GET/POST /members/v1/members | VERIFIED. dedup by loginEmail (gated PII; use a fallback member when absent). VERIFIED (2026-08-02, single-site observation): the target-site member list can already contain auto-created user-members for the owner/contributing Wix users (status: APPROVED; resolve by loginEmail, never by deriving from the account GUID — the observed id equality is undocumented) — never dedupe or count these against source-site members; DELETE /members/v1/members/{id} works for cleaning up importer-created members. Throughput: no bulk create; documented ≥1s spacing (10k ≈ 3h serial floor — plan + resume via crosswalk). Create sends no email and does not fire the signup automations trigger (source-confirmed) — silent by default. Activation policy (decided 2026-08-03): passwords are never imported, and passwordless members complete the standard forgot-password flow (confirmed). Delivery = post-import label-wave automation: owner creates one dashboard automation (trigger label added, e.g. migrated-2026 → owner-branded email: "Log in → Forgot password"); importer applies the label via API in batches for rate control/re-targeting; enable only after the import window (AUTO-02 pause). Never mass-send send-set-password-email (3h single-use link), and never fetch/store per-member reset links — they are credential-equivalent secrets and the no-link flow needs none. |
| Stores product (BULK — the scale path) | POST /stores/v3/bulk/products-with-inventory/create | UNVERIFIED bootstrap. Use this, not per-product creates, for any catalog above a handful — 1000 products is 11 calls instead of 1000. Creates up to 100 products with inline options, inline brand/ribbon/infoSections, all variants AND per-variant inventory items in one request. Five caps apply simultaneously and exceeding ANY ONE rejects the whole request: products ≤100, variantsInfo.variants ≤1000 total, options ≤100 total, modifiers ≤100 total, infoSections ≤100 total. With 2 options per product the options cap binds at 50 products, not 100 — so batch with lib/ndjson.js → readBatchesByLimits using the exported BULK_PRODUCT_LIMITS + storesProductBulkCost, never on record count alone. TRAP: bulk is NOT atomic — a 200 can carry per-item failures in results[].itemMetadata.success; never infer success from the HTTP status. TRAP: correlate results by itemMetadata.originalIndex, not response position. TRAP: bulkActionMetadata.undetailedFailures counts failures whose detail the server dropped; ignoring it silently loses records. returnEntity:false (default) still returns itemMetadata.id, which is all a crosswalk needs — pass true only for a contract probe that must inspect the created entity. bulkCreateStoresProductsWithInventory handles normalization, correlation and the unaccounted-item check, returning { results, succeeded, failed, undetailedFailures, unaccounted }. Normalization is purely local — HTML descriptions travel as plainDescription for Wix to convert server-side — so a batch is one HTTP call. It used to convert one description per product first, which made a 100-product batch 101 calls and reliably tripped the Ricos endpoint's 403 throttle. |
| Stores product | POST /stores/v3/products, POST /stores/v3/products/query, PATCH /stores/v3/products/{id}, GET /stores/v3/products/{id}, GET /stores/v3/products/slug/{slug} | VERIFIED (2026-07-05; media patch verified in migration-20260715-01). buildCreateStoresProductRequest runs normalizeStoresProductV3, which bakes in the create traps: PHYSICAL requires physicalProperties:{}; simple/V1-ish top-level price, sku, and physical properties are moved into a single V3 variant; option/variant choice names clamped to 50 (identical truncation both places); variant optionChoiceNames.renderType defaulted to TEXT_CHOICES; compareAtPrice dropped unless strictly > actualPrice; variant price must be a money object ({ actualPrice: { amount: "14.95" } }) — a bare string/number 400s "Expected an object" (verified 2026-07-19), so the normalizer coerces scalar prices. Descriptions: a string description is HTML and is moved to plainDescription by normalizeStoresProductV3; Wix converts that to rich content server-side, so the product path never calls the Ricos endpoint (description proper is a Ricos document object — HTML there is a type error). TRAP: plainDescription is silently ignored when description is also set — a 200 with an empty description — so the normalizer throws when both are present. TRAP: hard cap of 16,000 characters (the old Ricos path chunked at 28k and merged, so it was effectively unbounded); over-long bodies throw rather than truncate, and need an info section or an explicit mapping-gaps.json entry. For product media, prefer media.itemsInfo.items[] with external url values when the source URLs are publicly reachable: the Stores product API ingests them in the background, avoiding the slower, heavily-throttled Media Manager pre-import path. Use pre-imported Wix media ids only when the target flow requires them. Upsert lookups: getStoresProduct(wix, id) and getStoresProductBySlug(wix, slug) return the product or throw 404 — callers should catch and treat as null for "not found". Catalog sweeps: queryStoresProducts returns one page unwrapped to the array with the cursor discarded, so a crosswalk-recovery or name-match safety net must use queryAllStoresProducts(wix), which follows the cursor and throws rather than returning a partial index. Slugs: sanitizing is owned by lib/wix-build.js (toWixSlug, applied automatically via the coerce: 'slug' rule on product.slug), not by this module and deliberately not by normalizeStoresProductV3 — the writer passes the caller's slug through unchanged so URL preservation can record the original next to the derived plannedTargetSlug. It matters because Wix rejects anything outside [a-z0-9-] and slug validation precedes application, so one bad slug (Shopify mints ph-5_5 from a "pH 5.5" title) 400s the entire bulk batch rather than just that product. General patch: patchStoresProduct(wix, { productId, revision, ...fields }) applies normalizeStoresProductV3, so a string description moves to plainDescription here too; revision from the existing product is required. UNVERIFIED for get-by-id, get-by-slug, and general-field patch (endpoint VERIFIED; arbitrary-field patching and slug lookup not yet live-tested). Variable products / variant matrices: Wix Stores V3 requires server-assigned optionChoiceId GUIDs before a variant matrix can be built — those IDs do not exist until after the initial createStoresProduct call returns. A single-pass import therefore cannot create a full variant matrix in one call. The two-pass approach: (1) create the product with options declared (but no variants), (2) create each option choice via POST /stores/v3/products/{id}/options/{optionId}/choices to obtain server-assigned choice IDs, (3) create variants via POST /stores/v3/products/{id}/variants. When the two-pass flow is out of scope (e.g. public-data single-pass run), import as a single-variant product at the base price, record the fidelity loss in mapping-gaps.json, surface it in the execution-plan report, and ensure the generated product detail page renders productOptions when non-empty so the storefront is ready once options are added. |
| Stores category | POST /categories/v1/categories, POST /categories/v1/categories/query | VERIFIED (2026-07-05) for create. Body { category, treeReference } — treeReference is top-level (sibling of category, {appNamespace:"@wix/stores"}); nesting it 400s "treeReference must not be empty". Empty optional strings such as description: "" are omitted. Query: same top-level treeReference trap applies. queryStoresCategories(wix) returns one page (100 max) unwrapped to the array — it does not return all categories, and the unwrap discards the cursor so it cannot be paged. Any dedupe/upsert index must use queryAllStoresCategories(wix), which follows pagingMetadata.cursors.next and throws rather than returning a partial index. Observed 2026-07-29: a site with 119 categories read back as 100 through the single-page call, which would have duplicated the missing 19. UNVERIFIED for query (not yet live-tested). |
| Wix Tag (global) | POST /tags/v1/tags | UNVERIFIED bootstrap. Body { tag: { name, fqdn } }. FQDN for Stores V3 products: "wix.stores.catalog.v3.product". Max 100 tags per FQDN. Create tags before products; crosswalk tag IDs into product tags.publicTags.tagIds[] at create time. Tags are a native Wix entity — do not route product tags to CMS. |
| Stores inventory item | POST /stores/v3/inventory-items | UNVERIFIED bootstrap. { inventoryItem: { variantId, productId, inStock: true } } marks a variant as in-stock at the default location (omit locationId). Inventory items are not auto-created on product create — call this for each variant after createStoresProduct. For quantity-tracked stock use { quantity, trackQuantity: true } instead of inStock. setProductVariantsInStock(wix, { productId, variantIds }) is a convenience wrapper that calls createInventoryItem for each variant. Requires permission scope SCOPE.STORES.INVENTORY_ITEM_WRITE. |
| Stores category membership | POST /categories/v1/bulk/categories/add-item | VERIFIED (2026-07-05). { item:{ catalogItemId, appId }, categoryIds[], treeReference }; appId = Wix Stores 215238eb-…, catalogItemId = Wix product id. |
| Contacts | POST /contacts/v5/contacts, POST /contacts/v5/bulk/contacts/upsert, POST /contacts/v5/contacts/query, GET /contacts/v5/contacts/{id}, PATCH /contacts/v5/contacts/{id} | GA (docs verified 2026-08-04 — was Developer Preview when selected on 2026-07-26). Do not fall back to V4 unless a migration explicitly chooses a legacy compatibility path. The GA contact shape is FLAT — no info wrapper, no emails.items/phones.items list wrappers: one main email/phone (contact matching + subscription status live on these) plus additionalEmails/additionalPhones arrays; addresses[] keeps postal fields nested under address; company is { name, jobTitle }. Create and update both take { contact, allowDuplicates }; update requires the current revision and has no fieldMask. Phone tags: OTHER/MAIN/HOME/MOBILE/WORK/FAX (no UNTAGGED). buildCreateContactRequest/buildUpdateContactRequest accept the flat GA contact (legacy info payloads convert strictly — non-mechanical keys like extendedFields/labelKeys throw). Live create/query/update verification is still pending a token with Contacts permissions (2026-07-26 probe got 403) — keep V5 writers surfaced as UNVERIFIED in execution reports until a live contract test promotes them. Custom fields (GA): the V5 contact carries extendedFields.namespaces.<ns>; per the V5 contact-object docs, definitions go through the Data Extension Schema API with FQDN wix.contacts.*.contact (user-defined values under _user_fields). The V4 Contacts Extended Fields API (POST /contacts/v4/extended-fields, values under info.extendedFields) pairs with the V4 surface only — do not mix. CAVEAT: the Data Extension Schema intro's supported-objects table does not list contacts yet (docs inconsistency at GA cutover); verify the DES path live during setup before relying on it. Labels → tags: V5 exposes tags.privateTags.tagIds[] managed via the Tags API (same FQDN); V4 label APIs and the labels_added automation trigger are a V4-surface concept — resolve the tag/label story during setup before planning post-import label waves through V5 writers. |
| Coupons | POST /stores/v2/coupons, POST /stores/v2/coupons/query | UNVERIFIED bootstrap. Prefer native Wix coupons because Wix has a native coupon entity; do not special-case coupons into CMS merely for caution or because scoping must be mapped. Fallback is only for source semantics with no native representation. |
| eCom order | POST /ecom/v1/orders/import (historical orders), POST /ecom/v1/orders/query, POST /ecom/v1/orders/{orderId}/activities/add | VERIFIED LIVE (2026-08-12 reference-store run; re-verified 2026-08-16 with applied discounts, line-item description lines and merchant notes). Import Order is the only import-safe path — see ecom/order.json. POST /ecom/v1/orders (createOrder) remains BLOCKED for migration: it decrements inventory, emails the buyer and auto-creates contacts. TRAP: order.number must be numeric — a prefixed source number fails the whole call with a bare 400 {"message":"Not a numeric value"} and no field path. |
| Site notifications mute | POST /notification-preferences/v1/site-mute/mute, POST /notification-preferences/v1/site-mute/unmute, GET /notification-preferences/v1/site-mute | VERIFIED (2026-08-04, full cycle live on a test target: mute → state read → idempotent re-mute → unmute → state restored, all 200). Spec 0012. Mutes all notifications of the site in context — all recipients, all channels; sendability denied regardless of recipient-level preferences. All three calls return { siteMuteState: { muted, reason?, mutedBy: { wixUserId } } }; executors unwrap to siteMuteState. Mute body { reason?: string } (≤500, clamped by the builder) — always pass a project-identifying reason (RePlatform migration — <project>) so the mute is auditable. TRAP: re-mute overwrites reason (last caller wins) — the import preflight's idempotent re-call must pass the same reason as setup. AUTH TRAP: user tokens only — the CLI-minted OauthNG site token (WIX_AUTH_TOKEN from config/wix.env) works; an account API key gets a uniform empty-body 403 on all three endpoints. Hard invariant: when mute is in effect (always for WIX_SITE_STRATEGY=new; explicit opt-in for existing sites), a failed mute halts the run before any import write — no degraded mode. unmuteSiteNotifications is never called by the flow itself — explicit owner request only; after an on-request unmute, confirm muted: false via getSiteMuteState. |
| Direct native REST | any Wix REST path derived by codegen | UNVERIFIED generated path for native Wix entities missing a dedicated adapter writer. Must log, call notifyMissingWriter, and be shown in the execution plan. |
Safe-mode contact replacement
lib/wix-writers.js exports the shared safe-mode runtime:
createSafeModeConfig(env)
mockEmailForEntity(entityType, entityId)
sanitizeContactFieldsForSafeMode(value, options)
sanitizeWixRequestBody(body, options)Generated imports must pass safeModeOptions to writer builders/executors when
SAFE_MODE is enabled. Request builders sanitize a copied request body before returning
it and throw SafeModeBlockedError before any send when suspicious non-replaced email
values remain.
Safe-mode replacement paths use deterministic request-body paths with object fields, array wildcards, and Wix wrapper arrays, for example:
contact.email.email
contact.additionalEmails[].email
contact.phone.phone
order.billingInfo.email
dataItem.data.submissions[].emailReplacement only touches primitive leaf values — a generic path that lands on an object
(such as the GA Contacts V5 email object) is skipped rather than clobbered, and any raw
email left inside a skipped object still trips the suspicious-value block.
Target domain entity files may define safeModeContactFields[] with { kind, targetPath, source, notes } entries. Mapper/codegen must merge those target hints with source-side
contact evidence and user mappings, then pass resolved request-body paths to the shared
writers.
Stores subscription validation
lib/wix-writers.js exports the Stores subscription contract and deterministic helpers:
STORES_SUBSCRIPTION_CONTRACTnormalizeStoresProductSubscriptionsvalidateStoresProductSubscriptionDetails
Generated import code should call the normalizer from product transforms and the validator during dry-run and immediately before live create. Validation failures are record-level preflight failures/deferred outcomes; do not send known-invalid subscription payloads to Wix to discover field errors live.
Catalog V3 subscriptions are a native Stores target, not an automatic CMS fallback or
semantic loss. Product create with subscriptionDetails.allowOneTimePurchases and
subscriptions[] entries containing title, description, frequency, interval, and
autoRenewal was verified live on 2026-07-26 in the nopong migration; Wix returned
server-assigned subscription option ids. Mapping/codegen should emit native recurring product
creates when the source cadence is known. General subscription patching remains unverified.
Stores live verification helpers
Use the shared Stores verification CLI for live target checks that would otherwise become migration-local snippets:
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores subscription-create \
--artifact migrations/<project>/setup/stores-subscription-verification.json \
--proposal-artifact migrations/<project>/setup/contract-ledger-proposal.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-count \
--artifact migrations/<project>/execution/stores-product-count-verification.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores product-by-source-marker \
--marker-path customFields.sourceId --marker-value <source-id> \
--artifact migrations/<project>/execution/stores-product-marker-verification.json
node skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js stores delete-probe \
--product-id <probe-product-id> \
--artifact migrations/<project>/setup/stores-probe-cleanup.jsonThe CLI reads WIX_AUTH_TOKEN and WIX_SITE_ID from the environment or
config/wix.env under the current project root, creates the shared Wix client, and sends
requests through wix.send(...). It writes machine-readable artifacts with the target
site id, endpoint, method, verified nested paths, discovered constraints, probe id,
cleanup status, warnings, recovery instructions, and timestamp. Failed probe cleanup is
not hidden: the artifact must remain with an explicit warning and a delete-probe
recovery command.
Routed URL preservation behavior
URL preservation is a mapping and execution-state concern. This adapter owns only the Wix-side facts that generated code may rely on when deciding whether a final target URL is known.
Current import-only phase rules:
Do not configure Wix redirects or site routing.
Do not guess a destination relative URL from a slug when Wix route behavior has not been verified for that entity and delivery mode.
When the destination base path or final URL is unknown, write URL ledger rows with
urlStatus: "pending_target_route"ortarget_url_missingand record unresolved rows for the future website-builder phase.When a Wix create/update response or a safe declared lookup returns a final slug or relative path, generated code may record it as
actualTargetSlugoractualTargetRelativeUrland write a redirect plan if it differs from the source URL.
Known routed entity status:
| Entity | URL behavior contract |
|---|---|
| Blog posts | Create/publish is verified for content writes. Final public route exposure after publish is not verified here; generated code must treat the target route as pending_target_route unless a safe lookup in the generated write spec proves the final relative URL. |
| Blog categories | Category create is verified. Final public category route exposure is not verified here; default to pending_target_route. |
| Stores products | Product create and slug-bearing product operations are verified, but arbitrary route/base-path configuration is not part of this phase. Generated code may record actual target slugs only when returned or safely looked up; target relative URLs remain pending_target_route unless the route pattern is explicitly verified in the project. |
| Stores categories/collections | Category create is verified. Final collection/category route exposure is not verified here; default to pending_target_route. |
| CMS items with dynamic pages | CMS item writes are verified. Dynamic page route patterns are site-builder configuration, not native item import output; default to pending_target_route unless website-builder artifacts define the route. |
Shared execution runtime contract
In addition to endpoint primitives, this adapter should define the reusable runtime API that generated migrations call.
Conceptual contracts:
executeWriteSpec({
spec,
items,
client,
dryRun,
runContext,
}): Promise<WriteExecutionReport>
executeSetupPlan({
plan,
client,
dryRun,
runContext,
}): Promise<SetupExecutionReport>The exact filenames are implementation details, but the shared runtime should be the single place that owns generic Wix execution mechanics.
WriteSpec contract
Generated migrations should pass a declarative WriteSpec into the shared runtime.
Each spec should define, as applicable:
entitymode:create | update | upsert | bulk_create | bulk_upsertcreateupdatelookupbulkbatchSizeconcurrencyretryPolicythrottlePolicyauditKeyscrosswalkAuthority: "local"cmsMirrorcrosswalkStrategyreconciliationStrategydependencyRefsverificationLevel:verified | unverified
This resource should document how each field is interpreted by the runtime.
WriteExecutionReport contract
The shared runtime should return a compact, token-aware report suitable for the agent.
It should include:
run ID
entity
counts: input, attempted, created, updated, skipped, failed, retried, throttled
grouped error buckets
references to fuller on-disk logs
The runtime should prefer grouped summaries over raw per-record detail.
These per-entity or per-phase reports are intermediate runtime outputs. They are inputs to the final completion artifact, not replacements for it.
Completion artifact contract
The shared runtime should support deterministic emission of a final machine-readable completion artifact for the whole run.
Preferred output:
execution/completion-report.json
This artifact should be the authoritative source for:
final run status
final imported/updated/skipped/failed counts
separate dry-run counts when
dryRun: true: requests built, Wix calls skipped, would-create/update/delete, would require live lookup, and local validation failuresgrouped skipped/deferred outcomes
grouped failure outcomes
dashboard/editor/preview destinations
artifact references for audit/error/execution logs
The shared runtime should also support deterministic rendering inputs for:
execution/review/completion-summary.md
The rendered summary should be derived from completion-report.json, not independently
invented by the agent.
Audit log contract
The shared runtime should emit append-only machine-readable audit events.
Preferred format: NDJSON.
Each event should capture, at minimum:
timestamp
run ID
phase:
setup | importentity
operation
source ID when applicable
target ID when known
endpoint or primitive used
attempt number
result status
HTTP status or platform error code when applicable
latency
The run ID is required because audit files are append-only across dry-runs, retries, and
recovery passes. Completion reports should be generated from runtime counters for the
current run and may use audit aggregation only when filtered by run ID. The shared
lib/audit-summary.js helper enforces this filter for fallback/verification summaries.
Dry-run runtime contract
The shared Wix runtime exports createDryRunConfig, normalizeDryRunValue,
createWixClient, and createWixSetupExecutor.
Dry-run is enabled by DRY_RUN=true or by the generated entrypoint's --dry-run flag.
It is disabled by default. true, 1, yes, and on enable it; false, 0, no,
and off disable it.
When createWixClient({ dryRun: true }) is used:
authTokenandsiteIdmay be blank unless the generated project requires the site ID for a local artifact namespace;send()must not callfetch, SDK, MCP, or CLI transports;send()must capture the would-send request understate/attempts/wix-request-captures.ndjsonwhenprojectDirorrequestCapturePathis supplied;captured headers must omit
Authorizationand redact secret-like header/body keys;captured bodies must be after
SAFE_MODEcontact-value sanitization;response payloads must preserve the live shape expected by writer helpers, for example
{ contact },{ product },{ dataItem },{ file }, or an expected collection field such as{ contacts: [] }for skipped queries.
Dry-run placeholder target IDs are not live authority. If downstream local execution
needs them, write them only to state/crosswalk/dry-run-crosswalk.ndjson or keep them
in memory. Never append simulated target IDs to state/crosswalk/crosswalk.ndjson and
never upload them to a CMS crosswalk mirror.
When createWixSetupExecutor({ dryRun: true }) is used, each setup step must first be
reduced to a structured intent, then captured as planned_dry_run without invoking MCP,
CLI, SDK, or REST transports that access or mutate Wix account/site state.
Artifact authority at run completion
The shared runtime contract should preserve explicit artifact authority:
execution-log.mdfor chronology and operator/debug context onlycompletion-report.jsonfor final outcomecompletion-summary.mdfor deterministic user-facing rendering
The runtime should not force downstream consumers to reconstruct final outcome by reading raw audit logs or chronological execution logs.
Retry and throttling policy
This adapter should define one shared retry/throttling contract rather than letting each migration invent its own.
It should classify, at minimum:
retryable transport/network failures
retryable
429responsesretryable
5xxresponsesnon-retryable
4xxvalidation failures unless explicitly listed otherwise
When the server provides backoff hints such as Retry-After, the shared runtime should
honor them.
Bulk and fallback policy
The shared runtime should know:
when a native bulk primitive exists and is preferred
when to fall back from bulk to per-record writes
when an upsert requires lookup + create/update orchestration
when an unverified native path must be surfaced before execution rather than used silently
Media policy by target
Do not treat all media the same. The target domain decides whether media must exist in Wix first or can be ingested through the entity API itself.
Wix Stores products: prefer external URLs on
media.itemsInfo.items[]and let the product API ingest them in the background. This is the default path when the URLs are publicly reachable.Wix Blog cover images and other surfaces that require a Wix media id at create/update time: import media first, then pass the Wix media id into the entity payload.
Generic Media Manager import is a fallback/shared primitive, not the default for every entity carrying media.
Media import source URL reachability
The media primitive imports by URL: Wix servers fetch the provided sourceUrl. Public
HTTPS URLs are expected; localhost, 127.0.0.1, Docker-only hosts, and other
private-only URLs are not reachable by Wix during a live import. This is optional source
preparation and, as far as we know today, affects media import only.
If the source system is local, the migration should either:
expose the source through a public HTTPS tunnel, then pass/rewire media URLs to that public base URL; or
skip/defer media import and clearly state which media-dependent references will be missing until media is imported.
Ngrok quick setup for macOS:
brew install ngrok
ngrok config add-authtoken "<YOUR_AUTHTOKEN>"
ngrok http 8090
export WP_BASE_URL=https://<id>.ngrok-free.appValidate by real call — do not trust doc examples
Codegen-time MCP doc checks confirm an endpoint exists; they do not confirm the request shape works. The live wporg-news import proved doc examples can be wrong (lowercase Ricos plugins → 400) or incomplete (featured image field, tag body). The rule for this adapter:
Treat a shape as verified only after a real call succeeds — encode the working shape here with a
// VERIFIED:(or// VERIFIED-TRAP:) note and a date.A
// UNVERIFIED:primitive is allowed as a bootstrap point for generated code, but it is not a silent live-write permission. The execution plan must call it out, and setup verification must either promote it with a sandbox/live validation or route to fallback.Keep
tests/target-wix/contract-test.jscurrent: it issues one real call per primitive against a sandbox site and is the single place schema drift surfaces. Tests live in the repo'stests/tree rather than in this bundle, so the published product carries no test harnesses. Run from the repo root:bash tests/run-all.sh target-wix WIX_AUTH_TOKEN=... WIX_SITE_ID=... bash tests/run-all.sh target-wixWithout credentials the shape checks still run and the live calls are skipped.
tests/target-wix/ndjson-contract-test.jscoverslib/ndjson.jsand never needs credentials.For live verification runs, pass
--progress-log <path>and poll it perCONVENTIONS.md#progress-log-polling.Run on a cadence and after any Wix API change. A failing contract test — not a stranger's broken import — is how we learn the surface moved.
Stores catalog is Catalog V3 only — Catalog V1 is not supported
A migration only ever writes to a V3_CATALOG site. Catalog V1 is not a supported
destination: there is no V1 write primitive here, no V1 fallback, and none should be added
(a former V1 fallback was removed — it only masked real V3 errors as spurious 428s). A V1
site is a blocker the run halts on, not a variant the adapter, the mapper, or codegen
accommodates. The V3 create traps are handled in lib/wix-writers.js
(normalizeStoresProductV3 + the top-level treeReference builder), so callers never
re-hit them.
On a site this run created, a Stores install is guaranteed Catalog V3. This section
used to require verifying that live, after a 2026-07-30 incident where a site scaffolded
from the blank headless template, with Stores installed afterwards through the App
Installation API, came up V1_CATALOG — Catalog version is fixed at provisioning, so
every V3 write on that site failed and the site had to be abandoned. The Wix platform has
since changed: Catalog V3 is now guaranteed at provisioning regardless of scaffold
template, so that failure mode can no longer occur for a newly-created site, and neither
the --site-template commerce requirement nor the pre-write version gate apply anymore —
see 0079-catalog-v3-guaranteed-retire-v1-gate.md.
This does not extend to an existing site this run did not create — rp-execute-setup
("A V1_CATALOG verdict is terminal here") still treats that case as a live risk and
halts on it, since a pre-existing site's catalog version predates this guarantee.
What stays in codegen (not here)
Per-project field maps and ordering (which source field → which data key, the
media/author/taxonomy ref maps, upsert-by-key) live in the generated transforms/write
specs. This adapter holds the invariant Wix request shapes, transport, and shared
execution mechanics. Collection names and schemas (PodcastEpisodes, …) are
project-specific and come from the mapping plan.
Provisioning pointers (see rp-execute-setup)
Apps (Blog, Members) install via the App Installation API; ground
appDefIdfrom the official "Apps Created by Wix" table.Wix Data enablement (
WDE0110): install the Wix Data appappDefId e593b0bd-b783-45b8-97c2-873d42aacaf4via the App Installation API; afterwardPOST /wix-data/v2/collectionscreates NATIVE collections with noWDE0110(verified live). Fallback: a custom app with a data-collections extension (declares collections at install time, but can't express REFERENCE fields).
Scope & coverage
Wix has many apps/entities (Stores, Bookings, Events, Restaurants, Pricing Plans, CRM,
…). This adapter does not pre-build all of them. Coverage is demand-driven and
grows through reviewed releases. A migration may still target a native Wix entity before a
dedicated primitive exists; in that case codegen emits a native REST path using
sendDirectRest, logs the missing primitive, and calls notifyMissingWriter so the
RePlatform team can add the writer later.
Native target ladder when no dedicated writer exists:
Use the dedicated
rp-target-wixprimitive when one exists.If Wix has a native entity but no dedicated primitive, generate a native REST call from Wix MCP/docs-schema, mark it
UNVERIFIED, log it, callnotifyMissingWriter, and surface it in the execution plan before any write. This is not a silent live write.Use CMS only when there is no suitable native Wix entity, or when the native entity is explicitly rejected for fidelity/side-effect reasons. CMS is not a fallback for a missing adapter writer.
Halt if neither a native path nor an acceptable CMS/custom target exists.
The invariant: anything not backed by a verified primitive is surfaced to the user for consent before execution — never written silently.
Setup runtime expectations
This adapter should also define the shared setup-side runtime used by setup execution.
It should cover reusable mechanics for:
app installation and verification
Wix Data enablement verification
CMS collection and field provisioning when supported
setup step ordering/checkpointing
setup audit logging
setup execution reporting
rp-execute-setup should consume setup artifacts through this runtime rather than
re-implementing setup mechanics in skill-local prose or one-off scripts.
Contract-test expectations
Contract tests should validate not only request builders, but also the most important shared runtime invariants.
Examples:
verified request shapes still serialize correctly
retry policy classifies common Wix failures correctly
throttle/backoff handling honors server hints
compact reports preserve stable top-level fields
completion reports preserve stable top-level fields
audit events preserve the agreed machine-readable shape
Files
- ATTRIBUTION.mdAdded by AutoGineer831 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md44.5 KiB
- domains/README.md2.2 KiB
- domains/blog/docs-survey.json2.2 KiB
- domains/blog/domain.json747 B
- domains/blog/entities/category.json1.7 KiB
- domains/blog/entities/comment.json3.1 KiB
- domains/blog/entities/post.json1.9 KiB
- domains/blog/entities/tag.json1.4 KiB
- domains/bookings/docs-survey.json22.3 KiB
- domains/bookings/domain.json6.0 KiB
- domains/bookings/entities/booking.json23.8 KiB
- domains/bookings/entities/service-add-on.json12.2 KiB
- domains/bookings/entities/service.json16.2 KiB
- domains/cms/docs-survey.json7.1 KiB
- domains/cms/domain.json663 B
- domains/cms/entities/collection.json1.5 KiB
- domains/cms/entities/data-item.json2.2 KiB
- domains/cms/entities/reference.json1.5 KiB
- domains/crm/docs-survey.json8.4 KiB
- domains/crm/domain.json818 B
- domains/crm/entities/contact.json8.1 KiB
- domains/crm/entities/email-subscription.json1.7 KiB
- domains/crm/entities/label.json1.9 KiB
- domains/ecom/docs-survey.json13.5 KiB
- domains/ecom/domain.json998 B
- domains/ecom/entities/delivery-profile.json13.2 KiB
- domains/ecom/entities/discount-rule.json5.4 KiB
- domains/ecom/entities/order-applied-discount.json9.8 KiB
- domains/ecom/entities/order-line-item-options.json8.4 KiB
- domains/ecom/entities/order-line-item.json3.1 KiB
- domains/ecom/entities/order-merchant-note.json7.5 KiB
- domains/ecom/entities/order-refund.json6.0 KiB
- domains/ecom/entities/order.json16.6 KiB
- domains/ecom/entities/shipping-option.json7.8 KiB
- domains/events/docs-survey.json5.6 KiB
- domains/events/domain.json1.1 KiB
- domains/events/entities/category.json2.5 KiB
- domains/events/entities/event.json3.6 KiB
- domains/events/entities/registration-form.json5.4 KiB
- domains/events/entities/rsvp.json9.2 KiB
- domains/events/entities/ticket-definition.json5.5 KiB
- domains/forms/docs-survey.json5.6 KiB
- domains/forms/domain.json4.5 KiB
- domains/forms/entities/form-submission.json11.7 KiB
- domains/forms/entities/form.json14.0 KiB
- domains/gift-cards/docs-survey.json1.4 KiB
- domains/gift-cards/domain.json2.7 KiB
- domains/gift-cards/entities/gift-card-product.json6.3 KiB
- domains/gift-cards/entities/gift-card.json7.6 KiB
- domains/index.json57.7 KiB
- domains/media/docs-survey.json1.1 KiB
- domains/media/domain.json691 B
- domains/media/entities/media-file.json1.8 KiB
- domains/media/entities/media-folder.json1.7 KiB
- domains/members/docs-survey.json4.6 KiB
- domains/members/domain.json638 B
- domains/members/entities/badge.json1.6 KiB
- domains/members/entities/custom-field.json5.0 KiB
- domains/members/entities/member-about.json4.0 KiB
- domains/members/entities/member.json4.7 KiB
- domains/pricing-plans/docs-survey.json1.3 KiB
- domains/pricing-plans/domain.json2.1 KiB
- domains/pricing-plans/entities/plan-order.json13.9 KiB
- domains/pricing-plans/entities/plan.json10.0 KiB
- domains/sales-channels/docs-survey.json117 B
- domains/sales-channels/domain.json1.5 KiB
- domains/sales-channels/entities/amazon-shop-connect.json3.6 KiB
- domains/sales-channels/entities/ebay-shop-connect.json2.9 KiB
- domains/sales-channels/entities/google-youtube-connect.json3.8 KiB
- domains/sales-channels/entities/meta-catalog-connect.json4.3 KiB
- domains/sales-channels/entities/pinterest-catalog-connect.json2.9 KiB
- domains/sales-channels/entities/tiktok-catalog-connect.json2.3 KiB
- domains/schema.json3.6 KiB
- domains/seo/docs-survey.json6.2 KiB
- domains/seo/domain.json4.1 KiB
- domains/seo/entities/item-seo-tags.json10.7 KiB
- domains/seo/entities/seo-pattern.json6.5 KiB
- domains/seo/entities/site-seo-tags.json5.0 KiB
- domains/stores/docs-survey.json8.8 KiB
- domains/stores/domain.json1.5 KiB
- domains/stores/entities/back-in-stock-request.json7.6 KiB
- domains/stores/entities/brand.json6.4 KiB
- domains/stores/entities/category.json2.6 KiB
- domains/stores/entities/coupon.json4.4 KiB
- domains/stores/entities/inventory-item.json2.0 KiB
- domains/stores/entities/product-customization.json9.1 KiB
- domains/stores/entities/product-tag.json2.9 KiB
- domains/stores/entities/product.json4.5 KiB
- domains/stores/entities/promotion.json7.5 KiB
- domains/stores/entities/review.json14.8 KiB
- domains/stores/entities/subscription-product.json5.2 KiB
- domains/tax/docs-survey.json5.6 KiB
- domains/tax/domain.json5.7 KiB
- domains/tax/entities/manual-tax-mapping.json9.0 KiB
- domains/tax/entities/tax-group.json7.5 KiB
- domains/tax/entities/tax-region.json6.6 KiB
- domains/tax/entities/tax-settings.json3.4 KiB
- lib/audit-summary.jsScript1.5 KiB
- lib/contract-ledger.jsScript5.3 KiB
- lib/domain-knowledge.jsScript33.3 KiB
- lib/gift-card-balance-reconciliation.jsScript5.2 KiB
- lib/gift-card-build.jsScript35.3 KiB
- lib/gift-card-redemption-build.jsScript14.2 KiB
- lib/ndjson.jsScript10.1 KiB
- lib/order-channel-build.jsScript3.1 KiB
- lib/shipping-build.jsScript13.8 KiB
- lib/stores-verification.jsScript8.2 KiB
- lib/tax-build.jsScript12.0 KiB
- lib/value-utils.jsScript453 B
- lib/wix-build.jsScript11.2 KiB
- lib/wix-target-spec.jsScript14.3 KiB
- lib/wix-writers.jsScript151.2 KiB
- scripts/docs-survey-sync.jsScript5.4 KiB
- scripts/domain-knowledge-validate.jsScript2.8 KiB
- scripts/domain-knowledge.jsScript3.0 KiB
- scripts/verify-stores.jsScript3.8 KiB
domains/bookings/entities/booking.json
{
"schemaVersion": 1,
"domain": "bookings",
"entity": "booking",
"displayName": "Booking",
"capability": "commerce.bookings",
"target": {
"classification": "native",
"nativeEntity": "Booking",
"verifiedFqdn": null,
"idPolicy": "server-assigned",
"crosswalkRequired": true
},
"sourceAliases": [
{
"sourceSystem": "woocommerce",
"sourceEntity": "bookingOrderLineItem",
"sourcePlugin": "mwb-bookings-for-woocommerce",
"routes": [
"/wc/v3/orders"
],
"confidence": "medium"
}
],
"preferredWrite": {
"surface": "Bookings Writer V2",
"endpoint": "POST /bookings/v2/bookings",
"writerId": null,
"verification": "docs",
"importSafe": false,
"bulk": true,
"decision": "importSafe is FALSE and this is the blocking entity of the domain. An import path demonstrably EXISTS on paper - Create Booking accepts an arbitrary bookedEntity.slot.startDate/endDate, status CONFIRMED is settable at create by an identity with Manage Bookings permissions, and flowControlSettings.skipAvailabilityValidation exists precisely to bypass availability - so this is not a missing-capability gap. It is a notification-safety gap. sendSmsReminder DEFAULTS TO TRUE: unless every single create call explicitly sends sendSmsReminder:false, Wix Bookings is instructed to text contactDetails.phone 24 hours before the session. That default points the wrong way for an import, it is the only request-level control whose scope is documented in delivery terms, and it says nothing at all about the site's own Bookings confirmation and reminder emails. Nothing in the tree documents a suppression flag for those. See notification-suppression-unproven for exactly what would settle it. Until then every booking write is a guarded write that must be surfaced at the approval gate, and the site-wide mute is the only control we can actually point at."
},
"reliability": {
"status": "unknown",
"flags": []
},
"safeModeContactFields": [
{
"kind": "email",
"targetPath": "booking.contactDetails.email",
"source": "target-schema",
"notes": "Customer email on the booking. The Booking object states outright: 'If no contact with this email exist, a new contact is created' - so this field silently writes into CRM as well as Bookings."
},
{
"kind": "phone",
"targetPath": "booking.contactDetails.phone",
"source": "target-schema",
"notes": "The SMS destination. Create Booking: 'the customer receives an SMS 24 hours before the session starts. The phone number is taken from contactDetails.phone' - and sendSmsReminder defaults to true. In safe mode this field is the single highest-risk value in the domain."
}
],
"pitfalls": [
{
"code": "sms-reminder-defaults-to-true",
"severity": "blocker",
"summary": "sendSmsReminder has 'Default: true' on both Create Booking and Bulk Create Booking (read 2026-08-16). This is the inverse of every other notification control the KB has met - events/rsvp's disableNotifications and this API's own participantNotification.notifyParticipants both default to OFF, so a generated payload built from the required-field list alone is SAFE there and UNSAFE here. Omitting the flag opts every migrated customer into an SMS to their real phone number. Every create call in the generated script must set sendSmsReminder:false explicitly, and the script's own test must assert its presence on every element of a bulk request, not just the first."
},
{
"code": "notification-suppression-unproven",
"severity": "blocker",
"summary": "UNPROVEN AS OF 2026-08-16 - docs only, no live probe. What IS established from the docs: exactly two request-level notification controls exist on Create Booking / Bulk Create Booking, participantNotification.notifyParticipants (default false, governs an ad-hoc message the caller supplies in participantNotification.message, delivered over channels named in metadata as EMAIL and/or SMS) and sendSmsReminder (default TRUE, governs a 24-hours-before SMS to contactDetails.phone). What is NOT established: neither flag is documented to govern the SITE's own Wix Bookings automation - the booking confirmation email, the reminder emails, the staff/owner 'new booking' alert - and no suppression flag for those appears anywhere on the Bookings Writer V2 pages. Confirm Booking has the same two controls and no more. So an import that sets sendSmsReminder:false and leaves notifyParticipants at its false default has silenced the two documented channels and has NO documented control over the rest. WHAT WOULD SETTLE IT: (a) a statement from the Bookings team, or in the docs, enumerating which automations a Create Booking with status CONFIRMED triggers and whether any request-level flag suppresses them - the cheap owner ask; or (b) a controlled probe on a throwaway site with a real monitored mailbox and a real monitored phone number, creating one booking with the flags set and one without on an UNMUTED site - the pair is what makes the negative observable, exactly as recorded for events/rsvp. Until one exists, importSafe stays false."
},
{
"code": "past-dated-bookings-unverified",
"severity": "blocker",
"summary": "THE OPEN QUESTION FOR HISTORICAL IMPORT, and the docs do not answer it in either direction. Nothing on the Booking object, Create Booking, or Bulk Create Booking pages restricts bookedEntity.slot.startDate/endDate to the future, states a minimum date, or lists a past-date error - the documented failures are 403 UNAUTHORIZED_OPERATION and 428 VALIDATION_FAILURE ('Verify all required booking fields are provided and valid'), which is generic enough to swallow a past-date rejection without naming it. Do NOT read that silence as permission: the same silence around draft events in events/rsvp turned out to be a hard 400 the moment it was probed live. flowControlSettings.skipAvailabilityValidation is documented for availability CONFLICTS ('the booking is created regardless of availability conflicts'), not for time travel, so it may or may not cover a slot that has already elapsed. WHAT WOULD SETTLE IT: one create call against a past slot on the test site, with and without skipAvailabilityValidation - it is a single cheap probe and it decides whether this domain can carry booking history at all or only forward-dated bookings. Plan the import as if past bookings may be rejected until that probe is run."
},
{
"code": "payment-status-is-ignored-at-create",
"severity": "blocker",
"summary": "'All bookings are created with paymentStatus=UNDEFINED, ignoring the payment status you specify.' A historical booking that was paid in the source therefore CANNOT be imported paid in one call - the field is accepted and discarded. The documented way to set it is a second call, Confirm Booking (or Confirm or Decline Booking), which 'also updates the booking's paymentStatus, if you specify a new payment status'. This collides with the next pitfall: Confirm Booking only accepts bookings whose status is PENDING, CREATED or WAITING_LIST, so a booking created directly as CONFIRMED has ALREADY BURNED its one chance to set a payment status through this path. The correct sequence for a paid historical booking is create as CREATED, then Confirm Booking with revision and paymentStatus - never create as CONFIRMED first."
},
{
"code": "created-is-invisible-confirmed-needs-permission",
"severity": "blocker",
"summary": "Create Booking defaults to status=CREATED, and 'such bookings aren't visible in the Booking Calendar' - an import that leaves the default produces records the site owner cannot see in the product they actually use, which will read as a failed migration. Setting status=CONFIRMED at create is possible but restricted: 'Only identities with Manage Bookings permissions can set status=CONFIRMED.' Two knock-on facts: for appointment services Wix Bookings only populates bookedEntity.slot.eventId (i.e. creates the calendar session) when the status becomes CONFIRMED, so an unconfirmed import leaves no calendar footprint at all; and confirmation is also the moment most likely to trigger customer-facing automation, which is why this interacts directly with notification-suppression-unproven."
},
{
"code": "booking-creates-a-crm-contact",
"severity": "blocker",
"summary": "contactDetails.email on the Booking object: 'If no contact with this email exist, a new contact is created.' A booking import therefore writes into Contacts as a side effect - it can duplicate contacts already imported by crm/contact, and booking volume becomes contact volume. This is the same trap that was CORRECTED live for events/rsvp on 2026-08-16, where the KB had wrongly claimed RSVPs do not touch CRM; take the documented statement at face value here rather than repeating that mistake. Sequence bookings after crm/contact, reconcile source customers by email, and pass the crosswalked contactDetails.contactId so Wix matches instead of creating. Count the contact writes in the plan."
},
{
"code": "class-bookings-need-a-session-that-nothing-imports",
"severity": "blocker",
"summary": "The three service types take three different bookedEntity shapes and only one of them is self-sufficient. APPOINTMENT: bookedEntity.slot with startDate/endDate/resource/location - Wix Bookings creates the session itself on confirmation, so the import is complete. CLASS: bookedEntity.slot.eventId, a GUID of an EXISTING calendar event, and 'when you specify eventId, Wix Bookings automatically derives startDate, endDate, timezone, resource, and location based on the event details. Manually specified values are ignored' - so a class booking is impossible until the session exists. COURSE: bookedEntity.schedule.scheduleId. No entity in this domain creates class sessions or course schedules; that is the recorded events-v3 gap in the docs survey. Consequence for bookings/service's own mapping rule - a source booking product with per-slot capacity greater than 1 becomes a CLASS - is that such products can have their SERVICE imported but not their BOOKINGS. Detect the capacity>1 branch during planning and disclose it at the approval gate."
},
{
"code": "bulk-create-caps-at-twelve",
"severity": "warning",
"summary": "Bulk Create Booking (POST /bookings/v2/bulk/bookings/create) validates createBookingsInfo as minItems 1 / maxItems 12 - 'Max: 12 bookings'. That is an order of magnitude tighter than the sibling bulk endpoints this KB has met (Bulk Create Services 100, Bulk Create Add Ons 100), so a booking history of any size is a long chain of small calls: budget the run time and the rate-limit headroom accordingly, and make every batch crosswalk-guarded so a mid-chain failure resumes instead of duplicating. bulk is recorded as true because the endpoint exists, not because it is efficient."
},
{
"code": "flow-control-overrides-need-app-permissions",
"severity": "warning",
"summary": "The overrides an import actually needs each carry their own permission beyond Manage Bookings: skipBusinessConfirmation requires BOOKINGS.OVERRIDE_AVAILABILITY, skipSelectedPaymentOptionValidation requires BOOKINGS.MANAGE_PAYMENTS, skipAddOnValidation requires BOOKINGS.MANAGE_ADDONS, and skipAvailabilityValidation warns to 'make sure the call's identity has the required permissions'. A run that discovers this at import time fails with 403 UNAUTHORIZED_OPERATION per call. Verify the identity's permissions during setup, and note that the docs' own remedies - a higher app permission, or Velo elevation - are deliberate escalations that belong in the approval-gate disclosure, not in silently generated code."
},
{
"code": "a-site-installed-validation-plugin-can-veto-every-write",
"severity": "warning",
"summary": "The Bookings Validation Service Plugin lets a site (or an installed app) implement Validate Before Create and reject bookings by its own rules. On a target site that has one, an import can be refused for reasons that exist in no Wix doc and in no error the KB can enumerate ahead of time. Check for installed validation extensions during setup, and treat an unexplained systematic create failure as this before assuming the payload is wrong."
},
{
"code": "participants-are-a-required-one-of",
"severity": "warning",
"summary": "'You must specify either participantsChoices or totalParticipants.' Use totalParticipants (minimum 1) only when the service has fixed pricing and no variants; use participantsChoices when the service has service options and variants, and 'the call fails if the specified participantsChoices aren't among the supported service options and variants'. Since bookings/service maps source products to FIXED pricing with no variants, totalParticipants is the correct branch for this vertical - but it must be sent, as neither is defaulted."
},
{
"code": "selected-payment-option-required-only-for-wix-checkout",
"severity": "info",
"summary": "selectedPaymentOption (UNDEFINED | OFFLINE | ONLINE | MEMBERSHIP | MEMBERSHIP_OFFLINE) is required when the customer pays through a Wix eCommerce checkout, and the Create Booking call fails without it; 'for custom checkouts, you don't have to specify this field'. A historical import is neither - it recreates a settled record, not a purchase. OFFLINE is the honest value for a booking that was paid on the source platform's gateway, since that money never lands in Wix; skipSelectedPaymentOptionValidation exists if the chosen option is not among those the service offers."
},
{
"code": "revision-is-required-on-every-follow-up-call",
"severity": "info",
"summary": "Confirm Booking's only required parameter is revision, and revision 'increments by 1 each time the booking is updated, rescheduled, or canceled'. Any two-phase import (create, then confirm with paymentStatus) must carry the revision returned by the create response into the second call - do not assume 1, and re-read the booking if a retry loses the value."
}
],
"mappingGuidance": [
"Order this entity LAST in the domain: it needs a crosswalked service GUID (bookedEntity.slot.serviceId), the staff member's resource GUID, add-on and add-on-group GUIDs for bookedAddOns, and a CRM contact to match against. domain.json's defaultImportOrder service -> service-add-on -> booking is that dependency chain.",
"Two-phase per booking, and the order matters: (1) POST /bookings/v2/bookings with status omitted (CREATED), sendSmsReminder:false, no participantNotification, flowControlSettings as required by the source's confirmation rules; (2) POST /bookings/v2/bookings/{id}/confirm with the revision from step 1 plus paymentStatus (PAID for a settled source order, NOT_PAID otherwise, EXEMPT for a free booking). Creating as CONFIRMED in one call is possible with Manage Bookings but forfeits the ability to set paymentStatus, because Confirm Booking only accepts PENDING/CREATED/WAITING_LIST.",
"Map the source booking window to bookedEntity.slot.startDate/endDate in ISO-8601, and ALWAYS send bookedEntity.slot.timezone as an IANA name - the docs state the slot is permanently redisplayed to the customer in the timezone captured at booking time, so a wrong timezone here is a permanent per-record defect, not a display setting that can be fixed later.",
"bookedEntity.slot.resource.id is the staff member's RESOURCE GUID (the same resourceId that bookings/service puts in staffMemberIds), not a staff-member GUID. Specifying it triggers an availability check that fails the call if the resource is busy; omitting it defers resource assignment to confirmation. For a historical import against a single placeholder staff member, expect self-collisions across back-to-back source bookings and plan on flowControlSettings.skipAvailabilityValidation.",
"contactDetails: send the crosswalked contactId together with firstName/lastName/email/phone so Wix matches the existing contact instead of minting a new one. Do NOT also send formSubmission unless the booking form data is genuinely being migrated - when formSubmission is present Wix overwrites every contactDetails field from it and ignores what you sent, except contactId.",
"Source booking status maps onto the enum CREATED | CONFIRMED | CANCELED | PENDING | DECLINED | WAITING_LIST. CANCELED is reachable at create only as a literal status value; the Cancel Booking method is a lifecycle call whose notification behaviour is unexamined, so prefer setting the status over calling the lifecycle method during an import.",
"externalUserId is a free string on the Booking object and is the natural place to park the source booking id as a secondary key alongside the crosswalk. extendedFields exists but, as everywhere else in Wix, requires a schema-plugin app namespace configured in the app dashboard first - it is not a free-form carry.",
"totalParticipants (minimum 1) is the correct participant branch for this vertical; participantsChoices is only for services carrying options and variants, which bookings/service does not create.",
"Every booking that reaches Wix also reaches CRM. Reconcile the source customer list against the contact crosswalk before this step runs and count the contact writes in the plan report."
],
"setupRequirements": [
"Wix Bookings app installed (appId 13d21c63-b5ec-5912-8397-c3a5ddb27a97) - see the bookings-app-required shared pitfall",
"SCOPE.DC-BOOKINGS.MANAGE-BOOKINGS - also the permission that gates setting status=CONFIRMED at create",
"BOOKINGS.OVERRIDE_AVAILABILITY, BOOKINGS.MANAGE_PAYMENTS and BOOKINGS.MANAGE_ADDONS if the corresponding flowControlSettings overrides are used",
"bookings/service and bookings/service-add-on imported, with service, resource, add-on and add-on-group GUIDs in the crosswalk",
"crm/contact imported and reconciled by email, because every booking create touches Contacts",
"Site-wide notification mute confirmed active before any write, since it is the only suppression control this entity can actually point at",
"At least one staff member provisioned, with its resourceId known"
],
"evidence": [
{
"type": "wix-docs",
"url": "https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/booking-object",
"note": "object page: status enum CREATED|CONFIRMED|CANCELED|PENDING|DECLINED|WAITING_LIST with per-value semantics, paymentStatus enum UNDEFINED|NOT_PAID|PAID|PARTIALLY_PAID|REFUNDED|EXEMPT|FAILED, selectedPaymentOption enum, contactDetails.email creates a contact when none matches, bookedEntity slot-vs-schedule ONE-OF, bookedAddOns snapshot fields (maxItems 21), externalUserId, revision, appId 13d21c63-b5ec-5912-8397-c3a5ddb27a97, formSubmissionId/formId (read 2026-08-16)"
},
{
"type": "wix-docs",
"url": "https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking",
"note": "POST https://www.wixapis.com/bookings/v2/bookings (schema header also shows the internal /_api/bookings-service/v2/bookings form); permission SCOPE.DC-BOOKINGS.MANAGE-BOOKINGS; booking required, bookedEntity required, participantsChoices|totalParticipants required ONE-OF; 'Create Booking defaults to status=CREATED. Such bookings aren't visible in the Booking Calendar. Only identities with Manage Bookings permissions can set status=CONFIRMED'; 'All bookings are created with paymentStatus=UNDEFINED, ignoring the payment status you specify'; sendSmsReminder Default: true; participantNotification.notifyParticipants Default: false; flowControlSettings skipAvailabilityValidation / skipBusinessConfirmation / skipSelectedPaymentOptionValidation / skipAddOnValidation with their per-flag permissions; class bookings derive everything from eventId; errors 403 UNAUTHORIZED_OPERATION and 428 VALIDATION_FAILURE, and NO documented past-date restriction (read 2026-08-16)"
},
{
"type": "wix-docs",
"url": "https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/bulk-create-booking",
"note": "POST https://www.wixapis.com/bookings/v2/bulk/bookings/create; createBookingsInfo minItems 1, maxItems 12 ('Max: 12 bookings'); the same sendSmsReminder Default: true and participantNotification defaults repeat per element, so the unsafe default is per-booking not per-request (read 2026-08-16)"
},
{
"type": "wix-docs",
"url": "https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/confirm-booking",
"note": "POST https://www.wixapis.com/bookings/v2/bookings/{bookingId}/confirm; required parameter revision; 'You can only confirm bookings with a status of PENDING, CREATED, or WAITING_LIST'; 'Also updates the booking's paymentStatus, if you specify a new payment status'; same two notification controls and no others (read 2026-08-16)"
},
{
"type": "repo-research",
"path": "skills/wix-replatform/resources/rp-target-wix/domains/events/entities/rsvp.json",
"note": "the importSafe:false pattern this entry follows, and the two live corrections it warns against repeating: documented silence about a precondition (draft events) turned out to be a hard rejection, and a KB claim that guest records do not touch CRM turned out to be wrong"
},
{
"type": "repo-research",
"path": "skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json",
"note": "the source profile this domain was authored against; the live WP test site carried no booking records (150 orders, none of them a booking), so the source-to-booking FIELD mapping could not be observed - the /wc/v3/orders route claim in sourceAliases rests on the storage model, not on an observed payload"
},
{
"type": "repo-research",
"path": "skills/wix-replatform/resources/rp-source-wordpress/plugins/mwb-bookings-for-woocommerce.json",
"note": "sourceAliases added 2026-08-16 from the live probe of WPS Bookings for WooCommerce 3.12.0: the plugin registers NO booking post type and NO booking order type (authed /wp/v2/types on the reference store carries neither), so a booked appointment on WooCommerce is an ORDER LINE ITEM on /wc/v3/orders, flagged by order meta mwb_order_type='booking' and carrying its window in line-item meta (_wps_single_cal_date_time_from/_to, _wps_single_cal_booking_dates, or legacy _mwb_bfwp_date_time_from/_to). confidence 'medium': the route and the key names are established from the live REST index plus the published 3.12.0 source, but the test site holds zero booking orders so no payload has been observed."
}
],
"notes": "Authored 2026-08-16 to complete the bookings vertical. Docs-verified only, never exercised live - verification docs, reliability unknown. THREE things stand between this entity and a usable booking import and all three are cheap to settle: (1) whether a past-dated slot is accepted at all, one probe; (2) which site automations a create/confirm actually fires and whether anything suppresses them, one owner ask or one two-arm probe; (3) whether class sessions can be created, which is the events-v3 gap in the docs survey. sourceAliases carries ONE medium-confidence entry pointing at /wc/v3/orders, added 2026-08-16 from the live probe: WPS Bookings for WooCommerce registers no booking post type and no custom table, so a booked appointment IS a WooCommerce order line item carrying booking meta. The route claim is therefore structural and well-founded, but no booking record was ever observed - the test site holds 150 orders and not one of them is a booking - so the per-field mapping behind that alias is [NEEDS-VERIFICATION] and the line-item meta shape is still unknown. Note the asymmetry that makes this entity dangerous to generate code for: sendSmsReminder defaults to TRUE while every other notification control the KB has met defaults to off, so 'build the payload from the required fields' produces a safe call everywhere else in the repo and an SMS blast here."
}
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key, Needs a command-line tool, Needs Node.js
Security
What our scanner noticed· Medium risk
- Possible data-exfiltration endpoints
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Wix
- Publisher
- Wix (Official)
- Source
- wix/skills · wix-replatform/resources/rp-target-wix · commit d168bc2
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Wix.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download rp-target-wix.zip