RECIPE: Add Product to Cart

When to use this recipe

Any visitor request that boils down to "put product X (optionally size/color Y) into the cart". Examples:

  • "Add the red candle to my cart"
  • "Buy 2 of the cheapest sweater in size M"
  • "Add product <GUID> to my cart"

Inputs you need before STEP 4

InputHow to get it
Visitor tokenSTEP 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.
Quantityfrom the user (default 1)

Decision tree — pick the shortest path

  • User gave product GUID + variant GUID → STEP 1 + STEP 4.
  • User gave product GUID + picked options (size/color/etc.) → STEP 1 + STEP 3 (GET /products/{id}) + STEP 4.
  • User gave product name / "the X one" → STEP 1 + STEP 2 + (STEP 3 if options) + STEP 4.
  • User said "cheapest" / "any X" → STEP 1 + STEP 2 (paginate all, sort client-side — see warning below) + (STEP 3 if options) + 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.


STEP 1: Get a visitor token

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.


STEP 2: Find the product

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:

Copy

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):

Copy

Filter by name (use this when the user named a product):

Copy

What you need from the response

Copy
  • 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.

STEP 3: Resolve the variant GUID

Only needed when the product has variants (variantCount > 1) and the user specified an option (size, color, model…).

Primary path: GET the single product with variants inlined

GET https://www.wixapis.com/stores/v3/products/<PRODUCT_GUID>

Copy

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.

What you need from the response

Copy

How to pick the right variant

For each option the user picked (e.g. Size = "Large"):

  1. In 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.
  2. In 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).
  3. That variant's 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".

Do NOT use these endpoints for single-product variant resolution

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.

STEP 4: Add the line item to the current cart

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:

Copy

Always resolve the variant in STEP 3 first, then pass its GUID as options.variantId.

Simple add (product only, no variant):

Copy

With a specific variant (supply the variant GUID via catalogReference.options.variantId — keep catalogItemId as the product GUID):

Copy

STEP 5: Use the response

The response is { cart: { id, checkoutId, lineItems, ... } }. Save cart.id and cart.checkoutId — follow-up calls (update quantity, remove items, create checkout) need them.


Common pitfalls (from real conversations)

These are the ways this flow has failed in practice. The skill steps warn about them inline; this section is the consolidated reference.

1. Sorting V3 products by any actualPriceRange.* path returns 400

Copy

Same 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.

2. Reaching for /query-variants when you only need one product's variants

If 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.

3. add-to-cart with options.options.{Name: Value} silently returns an empty cart

Copy

The 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.

4. Confusing variant ID with catalogItemId (or with choice ID)

  • catalogReference.catalogItemIdproduct GUID (product.id from STEP 2 or /products/{id}).
  • catalogReference.options.variantIdvariant 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.

Constants

catalogReference.appId is the catalog provider's app ID, not the site or app GUID. For Wix catalogs:

CatalogappId
Wix Stores215238eb-22a5-4c36-9e7b-e7c08025e04e
Wix Bookings13d21c63-b5ec-5912-8397-c3a5ddb27a97
Wix Restaurants9a5d83fd-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.


References

Did this help?