Complementary Recommendations
Complementary recommendations
POST /api/v1/indexes/{index_name}/recommendations/complementary
Purpose
Drive cross-sell by suggesting items frequently purchased or interacted with together. Complementary recommendations use machine learning to analyze purchase patterns, co-occurrence data, and product relationships to identify items that naturally pair with what the customer is already considering. Unlike similar recommendations that offer alternatives, complementary suggestions work synergistically with the base product to optimize for increased Average Order Value (AOV).
When to use
- Cross-sell opportunities: When customers are viewing or have added items that commonly pair with accessories, add-ons, or complementary products
- Bundle optimization: To suggest logical product combinations that increase basket size
- Cart abandonment recovery: To add value perception through relevant add-ons
Example uses
| Use Case | Description | Input Products | Business Impact |
|---|---|---|---|
| Frequently Bought Together | Suggest items commonly purchased alongside the current product | Main product from PDP or cart | Drives cross-sell, leverages proven purchase patterns |
| Cart Completion | Recommend add-ons or accessories before checkout | All items currently in cart | Last-chance revenue boost, reduces post-purchase regret |
| Bundle Suggestions | Show products that work together as a logical set or package | Primary product or category anchor | Creates value perception, increases basket size |
| Cross-Sell Opportunities | Suggest complementary items from different categories | Any product as anchor point | Diversifies purchase, introduces new product categories |
Event types (eventType)
The eventType parameter controls which co-interaction signal is used to generate recommendations:
- ClickEvent — "customers also viewed". Based on products that users frequently view together.
- AddToCartEvent — "customers also added to cart". Based on products that users frequently add to cart together.
- PurchaseEvent — "frequently bought together". Based on products that users frequently purchase together.
How multiple docIds work
When multiple docIds are provided, results are products that are complementary to any of the input items. This is useful for checkout-page cross-sell where you want to recommend items based on the entire cart.
Example (cURL)
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/${index_name}/recommendations/complementary \
-H "x-marqo-index-id: ${MARQO_INDEX_ID}" \
-H "Content-Type: application/json" \
-d '{
"docIds": ["shirt_123"],
"eventTypes": ["ClickEvent", "AddToCartEvent", "PurchaseEvent"],
"limit": 6
}'
Multi-doc example (checkout cross-sell)
curl -X POST https://ecom.marqo-ep.ai/api/v1/indexes/${index_name}/recommendations/complementary \
-H "x-marqo-index-id: ${MARQO_INDEX_ID}" \
-H "Content-Type: application/json" \
-d '{
"docIds": ["shirt_123", "pants_456"],
"eventTypes": ["ClickEvent", "AddToCartEvent", "PurchaseEvent"],
"minFrequency": 5,
"limit": 4,
"filter": "in_stock:true"
}'
Parameters
Filters use the Marqo Filter DSL. The input item(s) are never returned. The endpoint uses strict validation: any unrecognised parameter returns a 400 error. sortBy is not accepted on this endpoint; results are ordered by co-interaction strength.
| Name | Type | Required | Description | Example |
|---|---|---|---|---|
| docIds | array[string] | yes | An array of 1-10 Marqo document IDs (the _id field) to anchor the complements. Note the name: this endpoint uses docIds, while /similar and /complete-the-look use documentIds. | ["shirt_123"] |
| eventType | string | one of eventType / eventTypes | A single co-interaction type: ClickEvent, AddToCartEvent or PurchaseEvent. | "PurchaseEvent" |
| eventTypes | array[string] | one of eventType / eventTypes | The co-interaction types to base recommendations on. At least one of ClickEvent, AddToCartEvent, PurchaseEvent. | ["ClickEvent", "AddToCartEvent", "PurchaseEvent"] |
| limit | integer | no | Max number of results (default 10). | 6 |
| offset | integer | no | Pagination offset (default 0). | 0 |
| filter | string | no | Constrain by stock, brand, category, price ranges. See Marqo Filter DSL. | "in_stock:true" |
| attributesToRetrieve | 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"] |
| minFrequency | integer | no | Minimum number of co-interactions required for a product to appear in results. Acts as a quality threshold; set higher to only surface strong signals. Must be a positive integer. Defaults to 1. | 5 |
| userId | string | no | Shopper identifier. Personalises the shelf when personalisation is enabled for the index. | "abc123" |
| sessionId | string | no | Shopper session identifier, used together with userId. | "xyz789" |
| profileId | string | no | A named search configuration profile for the request (e.g. different behaviour on PDP vs checkout). If the profile does not exist, Marqo falls back to the default configuration. | "pdp_default" |
| merchandisingProfileId | string | no | The merchandising profile whose rules (pins, exclusions, boosts, buries, filters) are applied to this shelf. This is separate from profileId, which selects search configuration. If no such profile matches, or the value is empty, the shelf's published rules apply. | "summer_campaign" |
Exactly one of eventType or eventTypes must be provided. Sending both, or neither, returns a 400.
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 with Complementary recommendations failed: <reason>.
How the shelf is filled
- Top-up for thin results: on the first page (
offset0), when the co-interaction search finds fewer than five results (or fewer thanlimit, iflimitis lower), the shelf is topped up with related products. Marqo takes products similar to the seeds and adds their complements, repeating up to three times until the target is met. Later pages (offsetgreater than 0) are not topped up. - Pins and merchandising rules: rules published for the Complementary Recommendations context in the Marqo console apply to the shelf. When exactly one
docIdsvalue is sent, the pins published for that product are placed at their positions; with several seeds, only the index-wide rules apply.merchandisingProfileIdswaps in the rules of a merchandising profile instead; a profile's per-product rules are looked up only when exactly one seed is sent. - Personalisation: when
userIdis sent and personalisation is configured for the index, results are re-ranked toward the shopper's interaction history. See Personalization.
Response (example)
{
"hits": [
{
"_id": "belt_789",
"_score": 1003.0,
"title": "Leather Belt",
"price": 59.0,
"image_url": "https://cdn.example.com/belt_789.jpg"
}
],
"processingTimeMs": 52,
"limit": 6,
"offset": 0,
"totalHits": 14,
"personalized": false
}
| Field | Type | Description |
|---|---|---|
| hits | array[object] | The recommended documents, each with _id, _score and the retrieved attributes. Pinned products carry the score of their neighbour so the shelf stays sortable by _score. |
| processingTimeMs | integer | Total server processing time in milliseconds, including any top-up rounds. |
| limit | integer | The limit applied to the request. |
| offset | integer | The offset applied to the request. |
| totalHits | integer | Total matching products, counting any pinned products inserted. When the shelf was topped up it is a single page, so totalHits equals the number of hits returned. |
| personalized | boolean | Present only when userId was sent. true when the results were re-ranked toward the shopper's history. |
| 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 | Present when userId was sent. true or false, matching the personalized field. |
Errors
Error responses have the body {"error": "<message>"}.
| Status | Message |
|---|---|
| 400 | Parameter 'docIds' is required and must be a non-empty array |
| 400 | All docIds must be non-empty strings |
| 400 | Number of 'docIds' must be less than or equal to 10 |
| 400 | Must provide exactly one of 'eventType' or 'eventTypes' |
| 400 | Parameter 'eventType' must be one of: ClickEvent, AddToCartEvent, PurchaseEvent |
| 400 | Each value in 'eventTypes' must be one of: ClickEvent, AddToCartEvent, PurchaseEvent |
| 400 | Parameter 'eventTypes' must be a non-empty array |
| 400 | Unrecognized param(s): '<name>' for any parameter not listed above, including sortBy |
| 400 | Complementary recommendations failed: <reason> when the underlying search rejects the request (for example, an invalid filter) |