Any visitor request that boils down to "put product X (optionally size/color Y) into the cart". Examples:
<GUID> to my cart"| Input | How to get it |
|---|---|
| Visitor token | STEP 1 (GenerateVisitorToken, or reuse one already in the conversation) |
Product GUID (catalogItemId) | STEP 2 (POST /stores/v3/products/query) — skip if the user gave it |
Variant GUID (options.variantId) | STEP 3 (GET /stores/v3/products/{id} → pick from inlined variantsInfo.variants[]) — only if the user picked a size/color/etc. |
| Quantity | from the user (default 1) |
GET /products/{id}) + STEP 4.STEP 3 default = GET /stores/v3/products/{id} (variants inlined at variantsInfo.variants[]). Avoid /query-variants unless you're listing variants across multiple products.
If you do not already have a visitor token in the conversation context, call the GenerateVisitorToken tool to mint one. Reuse the same token for every step of this flow — re-running add-to-cart with the same token merges items into the same cart, it does not create a new cart.
POST https://www.wixapis.com/stores/v3/products/query
WARNING — price is not server-side sortable in V3. No path under actualPriceRange is sortable. Do not send "sort": [{"fieldName": "actualPriceRange.minValue.amount", ...}] and do not send "sort": [{"fieldName": "actualPriceRange.minValue", ...}] — both return:
For "cheapest" / price-sorted requests: fetch up to paging.limit: 100 products and sort client-side on product.actualPriceRange.minValue.amount (the value is a string — parse to number before comparing).
List up to 100 products (use this for "cheapest" / "any" requests):
Filter by name (use this when the user named a product):
product.id is the product GUID. Pass it as catalogItemId in STEP 4.options[].choicesSettings.choices[].choiceId is a choice ID (the "Large" choice itself). It is not the variant ID — do not put it in options.variantId in STEP 4.variantSummary.variantCount tells you whether STEP 3 is needed:
0 or 1 → the product has no variants; skip STEP 3 and add by catalogItemId only.>1 and the user picked a size/color/etc. → continue to STEP 3.Only needed when the product has variants (variantCount > 1) and the user specified an option (size, color, model…).
GET https://www.wixapis.com/stores/v3/products/<PRODUCT_GUID>
This is one call scoped to one product — strictly better than the alternative below for single-product resolution. The response inlines all variants at product.variantsInfo.variants[] with their IDs and option-choice mappings.
For each option the user picked (e.g. Size = "Large"):
product.options[], find the option with name === "<option name>". Inside its choicesSettings.choices[], find the choice with name === "<value>" (e.g. "Large"). Grab that choice's choiceId.product.variantsInfo.variants[], find the variant whose choices[].optionChoiceIds.choiceId set contains every choiceId from step 1 (for multi-option products like Size + Color, all the user's choices must be present).id is your <VARIANT_GUID> — pass it as catalogReference.options.variantId in STEP 4.Never confuse variant ID with choice ID. Choice ID (<LARGE_CHOICE_ID>) identifies the option value "Large"; variant ID (<VARIANT_GUID>) identifies the specific product+option combination "Sculpted Candle in Large".
For one product + one user-picked option set, always use GET /products/{id} above. Don't reach for any of these:
POST /stores/v3/products/query-variants — works, but redundant: GET /products/{id} already inlines variantsInfo.variants[] for that product. Only use /query-variants if you genuinely need variants from many products in one call, and filter via productData.productId (NOT productId — that field returns 400 "not declared as filterable").GET/POST /stores/v3/products/{id}/query-variants — does not exist (404). There is no path-id form.POST /stores/v3/products/search-variants — does not exist (404). Not a real endpoint.POST https://www.wixapis.com/ecom/v1/carts/current/add-to-cart
WARNING — catalogReference.options.variantId is the only supported variant key. Do not pass option-name→value maps. The following body returns 200 OK with an empty cart (lineItems: []) — the line item is silently dropped:
Always resolve the variant in STEP 3 first, then pass its GUID as options.variantId.
Simple add (product only, no variant):
With a specific variant (supply the variant GUID via catalogReference.options.variantId — keep catalogItemId as the product GUID):
The response is { cart: { id, checkoutId, lineItems, ... } }. Save cart.id and cart.checkoutId — follow-up calls (update quantity, remove items, create checkout) need them.
These are the ways this flow has failed in practice. The skill steps warn about them inline; this section is the consolidated reference.
actualPriceRange.* path returns 400Same error if you try actualPriceRange.minValue, actualPriceRange.maxValue.amount, etc. No path under actualPriceRange is sortable.
Resolution: drop the sort clause, fetch with paging.limit: 100, parse product.actualPriceRange.minValue.amount (string) to a number, sort client-side.
/query-variants when you only need one product's variantsIf you have a product GUID, GET /stores/v3/products/<PRODUCT_GUID> inlines all variants at product.variantsInfo.variants[] in one call — that is the canonical variant-resolution endpoint in this flow. POST /stores/v3/products/query-variants is only for cross-product listings; if you do use it, the filter field is productData.productId (NOT productId — returns 400 "not declared as filterable"). And /stores/v3/products/{id}/query-variants and /stores/v3/products/search-variants are both 404 — they don't exist.
add-to-cart with options.options.{Name: Value} silently returns an empty cartThe cart API does not accept option-name→value maps. The only supported variant key under catalogReference.options is variantId. Resolution: resolve the variant GUID in STEP 3 first, then pass it as catalogReference.options.variantId.
catalogReference.catalogItemId ← product GUID (product.id from STEP 2 or /products/{id}).catalogReference.options.variantId ← variant GUID (variant.id from STEP 3, not choiceId).choiceId lives inside product.options[].choicesSettings.choices[].choiceId — it identifies an option value (e.g. "Large"), not a variant. Never put it in a cart line item.catalogReference.appId is the catalog provider's app ID, not the site or app GUID. For Wix catalogs:
| Catalog | appId |
|---|---|
| Wix Stores | 215238eb-22a5-4c36-9e7b-e7c08025e04e |
| Wix Bookings | 13d21c63-b5ec-5912-8397-c3a5ddb27a97 |
| Wix Restaurants | 9a5d83fd-8570-482e-81ab-cfa88942ee60 |
This endpoint requires visitor or member authentication. Calling it with an account or admin token will fail — always route through GenerateVisitorToken + CallWixSiteAPI.
The same /ecom/v1/carts/current/add-to-cart endpoint works for both Catalog V1 and V3 stores; only the product discovery (STEP 2) and variant resolution (STEP 3) endpoints differ by catalog version. This recipe targets V3, which is what the visitor MCP's <site-context> will report on a V3 site.