Recommendation Widgets
Recommendation widgets
POST /api/v1/indexes/{index_name}/recommendations/widgets/{widget_id}
Purpose
Serve a recommendation shelf that a merchandiser built and named in the Marqo console, such as a "New arrivals" carousel on the home page, a "Staff picks" grid on a landing page, a personalised "For you" row, or a "More in this colour" shelf on a product page. The storefront asks for the widget by its ID and Marqo returns the products the widget's source and merchandising rules produce. Merchandisers change what a widget shows from the console, without a theme or code change.
Background
The other recommendation endpoints are algorithms seeded by the request: similar and complementary take product IDs, For You takes a shopper. A widget is a configured object instead. Each widget has a name, an ID the storefront references it by, a source (shown in the console as its Primary endpoint) that fills it, and its own merchandising rules (pins, exclusions, boosts and buries, filters, and schedules) that apply only to that widget.
| Source | What fills the widget | Typical use |
|---|---|---|
| Search query | The results of a search query, merchandised with the widget's rules | "New arrivals", "Under $50", "Gifts for dad" |
| Collection | The products of a collection, merchandised with the widget's rules | "Best of bedding" on the home page |
| Hand-picked | Only what the merchandiser pins, in the order they chose, from anywhere in the catalog. Filters, boosts and buries shape anything after the pins | Editorial or campaign sets |
| For You | Personalised per shopper from their tracked events. Pinned products always appear; the remaining slots are filled per shopper at request time | Home page "For you" row |
| Similar products | Products similar to the seed product the storefront names in the request, as Similar Recommendations returns them | "You may also like" on a product page |
| Complementary products | Products shoppers buy alongside the seed product, as Complementary Recommendations returns them | "Frequently bought together" |
| Complete the look | Products that complete an outfit or set with the seed product, as Complete the Look returns them | "Complete the look" on a product page |
The last three are seeded widgets: each request names the product the shopper is viewing, and the widget can be tuned per product. See Seeded widgets.
When to use
- Home page and landing page shelves: Recommendations that are not tied to a product the shopper is viewing
- Campaign placements: Shelves a merchandiser retargets, reorders, or schedules without a deploy
- Personalised rows with guaranteed placements: A For You widget where certain products must always appear
- Product page shelves a merchandiser controls: A seeded widget that recommends from the product being viewed, restricted to products that share its colour, brand or type, with per-product pins and exclusions
- Any placement rendered from an ID: One theme block that renders whichever widget a merchandiser assigns to it
Example uses
| Use Case | Description | Source | Business Impact |
|---|---|---|---|
| Home page "New Arrivals" | Show recently added products, filtered to the last week and in stock | Search query | Keeps the home page fresh without theme edits |
| Seasonal campaign shelf | Hand-pick the campaign's hero products in a fixed order, scheduled for the campaign window | Hand-picked | Guarantees placement for promoted items |
| Collection spotlight | Feature a collection on the home page with a bestseller pinned first | Collection | Drives traffic into a priority category |
| Personalised "For You" row | Fill a row per shopper, with a new launch pinned to position 1 | For You | Combines personalisation with merchandising control |
| "Shop the colour" on a product page | Similar products that share the viewed product's color, with a hero product pinned on the bestseller's page | Similar products | Keeps product page recommendations on theme and lets merchandisers steer key pages |
| Landing page grid | Render a widget as a grid rather than a carousel; the API is layout-neutral | Any | One widget serves any layout |
Creating a widget (console)
- In the Marqo console open Merchandising → Recommendation Rules → Widgets and select the index.
- Click Create Widget and give it a Name, an ID, and a Primary endpoint (its source). A Search query widget also takes the query; a Collection widget takes the collection; a seeded widget can take up to three Match seed fields.
- Merchandise it as you would a search or collection: pin and exclude products, boost and bury, filter, and schedule rules. A seeded widget can also carry rules for individual seed products on its Product-level rules tab.
- Publish. A widget is served once it has published rules. Until then the endpoint returns
404for its ID.
Published changes reach the API within five minutes: rules are exported to the edge on a five-minute schedule. The same applies to a seeded widget's product-level rules, and when a widget is deleted, after which its ID returns 404.
The ID is what the storefront sends in the path. It is chosen by the merchandiser, is unique within the index, and is used exactly as authored (case-sensitive). IDs may contain letters, digits, _ and -, must start with a letter or digit, and are at most 64 characters long, so they are always URL-safe.
A widget's merchandising rules are independent: the index-wide merchandising rules (its pins, exclusions, boosts and buries) do not apply to a widget, only the rules authored on the widget itself.
The index's search configuration still applies, exactly as it does on /search and /collections. That includes the configured filter, score modifiers, collapse fields, attribute list and any matching query rules, so a widget cannot escape a global filter such as published:true. A For You widget runs under the For You configuration layered over the search configuration.
For You widgets cannot be previewed in the console, since their unpinned slots are chosen per shopper at request time.
Seeded widgets
A Similar products, Complementary products or Complete the look widget is seeded: it recommends from a product the request names, usually the product on the page. Every request to a seeded widget must name a seed, and a request to any other widget must not.
Name the seed one of two ways, exactly as on the underlying recommendation endpoint:
- By product:
seedParentProductIds, the product'sparentProductId. Marqo looks up the product's documents and seeds the shelf with some of them (see How many documents seed the shelf). The response'sseedDocumentIdslists the documents it used. - By document:
documentIds, the_ids of specific documents.
Send exactly one of the two. The seed product itself is never returned in its own shelf. A product with no documents in the index returns an empty shelf.
How many documents seed the shelf
A product in the index is usually several documents, one per variant: each colour or size of a dress is its own document, and they share the product's parentProductId. Recommendations are computed from documents, not products, so a request that names a product has to be turned into documents first. seedLimit sets how many of each product's documents are used: 3 by default, from 1 to 10.
- The documents are the first
seedLimitthe lookup returns for the product. They are not ranked, so they are not necessarily the most popular or representative variants.seedDocumentIdsin the response shows which were used. - A shelf takes at most 10 seed documents, so
seedParentProductIds×seedLimitmay not exceed 10. One product can use anyseedLimitup to 10. Four products at the default of 3 would be 12 documents and return400; send them with aseedLimitof 2. - Match seed fields are not limited by
seedLimit: the seed's values are read from all of the product's documents (up to 100), so a product sold in black and red matches both colours. - With
documentIdsyou choose the exact documents yourself, soseedLimitis not accepted.
Raise seedLimit when a product's variants differ enough that seeding from more of them gives better recommendations. Lower it, even to 1, when variants are near-identical, or to fit several products within the 10-document cap.
When several products are named, their documents are looked up together, up to products × seedLimit documents in total. A product with more documents than seedLimit can take up more of that total than its share, leaving a product named after it with fewer seeds, or none. A single product, which is what a product page sends, always gets up to seedLimit documents.
Match seed fields
A seeded widget can name up to three match seed fields, set in the console when the widget is created or with Edit next to the fields on the widget's page. Results are then restricted to products that share the seed's value for every one of those fields. A "Shop the colour" widget is a Similar products widget matching color: seeded with a black dress, it returns only products whose color is Black.
- Within a field, any of the seed's values matches. A list field matches on any of its elements, and a product seeded by
seedParentProductIdscontributes the values of all its variants (up to 100). - Across fields, all must match. A widget matching
colorandpricereturns only products with the seed's colour and its price. - The restriction is combined with the request's
filterand the widget's own filter rules. - Pinned products are not restricted by the match fields, so a merchandiser can pin a product that does not share the seed's values.
If the seed has no value for a match field, nothing can match it: the request returns 200 with no hits and an unmatchedSeedFields array naming the missing fields, and no recommendation search runs. The same empty response is the correct answer when no other product shares the seed's values. For example, a widget matching color and price returns nothing for the only product in its colour at its price.
Product-level rules
A seeded widget's Widget rules tab holds rules for every request, whatever product it is called for. The Product-level rules tab holds rules for one seed product: choose the product, preview the shelf the storefront gets for it, and pin, exclude, filter, or boost on top of the widget rules. Products with rules lists every product that has its own rules.
When a request is seeded with that product, its rules are layered over the widget rules:
| Rule | How product-level and widget rules combine |
|---|---|
| Pins | The product's pins take their positions; widget pins keep the remaining positions. |
| Exclusions | Both lists apply. A product-level pin overrides a widget exclusion of the same product, and a product-level exclusion removes a widget pin. |
| Filters | Both apply ((product) AND (widget)), unless the product's rules are set not to inherit the widget's filters. |
| Boosts and buries | Combined, with weights added on a shared field, unless the product's rules are set not to inherit the widget's boosts. |
| Recency | The product's recency setting if it has one; otherwise the widget's, unless the product's rules are set not to inherit the widget's boosts. |
Product-level rules apply only when every seed in the request belongs to one product: a single seedParentProductIds entry, or documentIds that all belong to the same parent product. A request seeded with several products gets the widget rules alone. The match seed fields still apply, matching any of the seeds' values.
The console's preview runs the same path as the API, so the shelf previewed for a product is the shelf a request seeded with that product returns.
If every seed document has been removed from the index, a Similar products widget falls back to the whole catalogue under the widget's rules, rather than returning an error. Complementary products and Complete the look widgets return the same errors their endpoints do.
Example (cURL)
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/${index_name}/recommendations/widgets/new-arrivals \
-H "x-marqo-index-id: ${MARQO_INDEX_ID}" \
-H "Content-Type: application/json" \
-d '{
"limit": 8,
"filter": "in_stock:true"
}'
Seeded example
A Similar products widget, called from a product page with the product being viewed:
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/${index_name}/recommendations/widgets/shop-the-colour \
-H "x-marqo-index-id: ${MARQO_INDEX_ID}" \
-H "Content-Type: application/json" \
-d '{
"seedParentProductIds": ["8123456789"],
"limit": 8
}'
For You example
A For You widget needs the shopper, exactly as the For You endpoint does. It requires the event tracking pixel to be installed on the store.
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/${index_name}/recommendations/widgets/for-you-home \
-H "x-marqo-index-id: ${MARQO_INDEX_ID}" \
-H "Content-Type: application/json" \
-d '{
"userId": "abc123",
"limit": 12
}'
A shopper Marqo cannot personalise for yet (no tracked events) still gets the widget: its pinned products first, then the widget's rules applied across the catalog. The x-marqo-results-personalized response header says which happened.
A For You widget returns a single synthesised page, so totalHits equals the number of hits returned rather than a catalog-wide count.
Parameters
The widget is named by the path. The body carries only per-request options and uses strict validation: any unrecognised parameter returns a 400 error. Filters use the Marqo Filter DSL and apply in addition to the widget's own filter rules.
| Name | In | Type | Required | Description | Example |
|---|---|---|---|---|---|
| widget_id | path | string | yes | The widget's ID as set in the console. Used exactly as authored (case-sensitive). | new-arrivals |
| limit | body | integer | no | Max number of results to return. Defaults to the index's configured search limit (12 if none is set); 10 for For You and seeded widgets. | 8 |
| offset | body | integer | no | Offset for pagination. | 0 |
| filter | body | string | no | Server-side constraints (e.g., in_stock, price ranges, brand, category). See Marqo Filter DSL. | "in_stock:true" |
| attributesToRetrieve | body | array[string] | no | Attributes to return for each document. If omitted, the attribute list configured for the index is returned, the same default as search. | ["title", "price", "image_url"] |
| seedParentProductIds | body | array[string] | seeded widgets (one of) | The seed product(s), by parentProductId. Up to 10; each value may contain only letters, digits, _ and -. Seeded widgets only. | ["8123456789"] |
| seedLimit | body | integer | no | The most documents (variants) of each seed product the shelf is seeded with, 1-10. Default 3. seedParentProductIds × seedLimit may not exceed 10. Only accepted with seedParentProductIds. See How many documents seed the shelf. | 3 |
| documentIds | body | array[string] | seeded widgets (one of) | The seed document(s), by _id. Up to 10. Seeded widgets only. | ["sku_38702"] |
| userId | body | string | For You widgets | User identifier for personalisation. Required for a For You widget and ignored by the other sources, so a storefront block can always send it. | "abc123" |
| sessionId | body | string | no | Optional session identifier. For You widgets only. | "xyz789" |
| interactionTypes | body | array[string] | no | Types of interactions to consider (ClickEvent, AddToCartEvent, PurchaseEvent). Default: all types. For You widgets only. | ["ClickEvent", "AddToCartEvent"] |
limit and offset must be whole, non-negative numbers. The API does not check this itself: a fractional or negative value is passed to the search engine, which rejects the request. For Search query, Collection and Hand-picked widgets, limit plus offset must not exceed 10,000.
Response (example)
{
"hits": [
{
"_id": "sku_38702",
"_score": 0.7412,
"title": "Linen Duvet Cover",
"price": 249.0,
"image_url": "https://cdn.example.com/sku_38702.jpg"
}
],
"limit": 8,
"offset": 0,
"totalHits": 143,
"processingTimeMs": 41
}
| Field | Type | Description |
|---|---|---|
| hits | array[object] | The widget's documents, each with _id, _score and the retrieved attributes. |
| limit | integer | The limit applied to the request. |
| offset | integer | The offset applied to the request. |
| totalHits | integer | Number of matching documents. For For You and seeded widgets this is the number of hits returned, not a catalog-wide count. |
| processingTimeMs | integer | Server processing time in milliseconds. |
| seedDocumentIds | array[string] | Seeded widgets called with seedParentProductIds only. The documents the seed products resolved to; empty when a product has none in the index. |
| unmatchedSeedFields | array[string] | Seeded widgets only, present when the seed has no value for one or more match seed fields. Those fields; hits is empty. |
These are the only fields the endpoint returns. Unlike a search response there is no facets, query, personalized or redirect field, so a renderer shared with the search page should not depend on them.
| Header | Description |
|---|---|
x-marqo-cache-hit-count, x-marqo-cache-miss-count | How many of the underlying searches were served from cache and how many were not. |
x-marqo-results-personalized | For You widgets only. true when the results were built from the shopper's own events; false when the widget fell back to its rules alone. |
Errors
Error responses have the body {"error": "<message>"}.
| Status | Message |
|---|---|
| 400 | Unrecognized param(s): '<name>' for any parameter not listed above |
| 400 | Parameter 'userId' must be a non-empty string when userId is present but empty |
| 400 | A message from the request validator when a value fails its constraint, for example an unknown interactionTypes value |
| 400 | Custom recommendations failed: Parameter 'userId' is required for a For-You carousel when a For You widget is called without userId |
| 400 | Custom recommendations failed: Personalized recommendations not configured, please contact Marqo to enable them for a For You widget on an index that has no event tracking pixel or has personalisation switched off |
| 400 | Custom recommendations failed: Widget '<id>' requires 'seedParentProductIds' or 'documentIds' when a seeded widget is called without a seed |
| 400 | Custom recommendations failed: Widget '<id>' does not take a seed product when a widget that is not seeded is called with seedParentProductIds, seedLimit or documentIds |
| 400 | Provide exactly one of 'documentIds' or 'seedParentProductIds', not both |
| 400 | Parameter 'seedLimit' is only accepted alongside 'seedParentProductIds' |
| 400 | <n> 'seedParentProductIds' at a 'seedLimit' of <m> could name <n×m> seed documents; at most 10 are allowed |
| 400 | Each 'seedParentProductIds' value must be a non-empty string of letters, digits, '_' or '-' |
| 400 | Custom recommendations failed: <reason> when the underlying search rejects the request (for example, an invalid filter) |
| 404 | Widget '<id>' not found: no widget with that ID on the index, the widget has no published rules yet, or merchandising is not enabled on the index |
Failures raised while the widget is being served carry the Custom recommendations failed: prefix. Request validation messages and the 404 do not.
Treat a 404 as "render nothing". It is the expected response for a widget a merchandiser has created but not yet published, or has since deleted.