Recommendations
Add a "Related products" or "You might also like" block to your product pages. Given one product, the recommendations endpoint returns the most similar products from the same catalog — computed from the data already indexed in Cognito Search (product name, description, brand, category, availability, and popularity). Nothing needs to be precomputed.
The endpoint is public and is called directly from the visitor's browser, the same way the search widget calls the search API. No API key or server-side code is required.
GET https://search-dev.k8s-vs.cognito.cz/api/recommendations/related
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceId | string | yes | UUID of your search instance. |
sourceId | string | yes | UUID of the products source the seed product belongs to. |
shopItemId | string | yes | The seed product's shop item ID (1–200 characters). |
limit | integer | no | Number of products to return, 1–20. Defaults to 6. |
Find instanceId under Instances → Embed widget and sourceId under
Instance → Sources in the admin panel. The source must be of type
products. shopItemId is the identifier from your products XML feed —
the same value the search API returns as externalId.
Your site's origin must be on the instance's domain allowlist (Instance → Domains), exactly as for the search widget.
GET https://search-dev.k8s-vs.cognito.cz/api/recommendations/related?instanceId=11111111-1111-1111-1111-111111111111&sourceId=22222222-2222-2222-2222-222222222222&shopItemId=SKU-12345&limit=6
{"seedShopItemId": "SKU-12345","strategy": "related","results": [{"externalId": "SKU-67890","title": "Wireless Keyboard K2","url": "https://shop.example.com/wireless-keyboard-k2","annotation": "Compact Bluetooth keyboard with backlight.","imageLinkS": "https://shop.example.com/img/k2-s.jpg","imageLinkM": "https://shop.example.com/img/k2-m.jpg","imageLinkL": "https://shop.example.com/img/k2-l.jpg","price": "1 290 Kč","productCode": "K2-BLK"}]}
| Field | Type | Description |
|---|---|---|
seedShopItemId | string | Echoes back the shopItemId you requested. |
strategy | string | Which strategy produced the results: related, category, or popular (see below). |
results | array | Related products, most relevant first. At most limit items; never includes the seed. |
Each item has the same shape as a product search result, so you can reuse
your search-result template. Only externalId and title are guaranteed;
treat every other field as optional.
| Field | Type | Always present | Description |
|---|---|---|---|
externalId | string | yes | The product's shop item ID. Use as a stable key. |
title | string | yes | Product name. |
url | string | no | Link to the product page. |
annotation | string | no | Short description, HTML stripped, truncated to 200 characters. |
imageLinkS | string | no | Small image URL. |
imageLinkM | string | no | Medium image URL. |
imageLinkL | string | no | Large image URL. |
price | string | no | Pre-formatted price string from the feed. |
availabilityRankText | string | no | Availability label. Present only for out-of-stock products. |
productCode | string | no | Product/manufacturer code from the feed. |
strategy tells youThe endpoint always tries the strongest match first and falls back so the
block is never empty on a healthy source. The strategy field reports
which level actually produced the results, so you can adapt the heading.
strategy | Meaning | Suggested heading |
|---|---|---|
related | Genuine similarity (matching name/description, brand, or category). | "Related products" |
category | No close match — popular products from the seed's category. | "More from this category" |
popular | No category match — the most popular products in the source. | "Popular products" |
You can ignore strategy and always show "Related products"; it is
provided so you can be more precise if you want to be.
Drop an empty container into your product page and run this once the current product's ID is known:
<section id="related-products" hidden><h2>Related products</h2><ul></ul></section><script>(async function () {var API_URL = 'https://search-dev.k8s-vs.cognito.cz';var INSTANCE_ID = '11111111-1111-1111-1111-111111111111';var SOURCE_ID = '22222222-2222-2222-2222-222222222222';var seedShopItemId = 'SKU-12345'; // the product on this pagevar url =API_URL +'/api/recommendations/related' +'?instanceId=' +encodeURIComponent(INSTANCE_ID) +'&sourceId=' +encodeURIComponent(SOURCE_ID) +'&shopItemId=' +encodeURIComponent(seedShopItemId) +'&limit=6';try {var res = await fetch(url);if (!res.ok) return; // 404/503 — leave the block hiddenvar data = await res.json();if (!data.results.length) return;var section = document.getElementById('related-products');var list = section.querySelector('ul');data.results.forEach(function (p) {var li = document.createElement('li');var a = document.createElement('a');a.href = p.url || '#';if (p.imageLinkM) {var img = document.createElement('img');img.src = p.imageLinkM;img.alt = p.title;a.appendChild(img);}var name = document.createElement('span');name.textContent = p.title;a.appendChild(name);if (p.price) {var price = document.createElement('span');price.textContent = p.price;a.appendChild(price);}li.appendChild(a);list.appendChild(li);});section.hidden = false;} catch (e) {// Network error — leave the block hidden; the page still works.}})();</script>
Key behaviors to copy into any integration:
externalId as the key.| Status | Body | Meaning |
|---|---|---|
404 | { "error": "Instance not found" } | instanceId is wrong or the instance was deleted. |
404 | { "error": "Source not found" } | sourceId is wrong, inactive, not of type products, or not in this instance. |
404 | { "error": "Product not found" } | No product with that shopItemId exists. Verify you pass the feed's shopItemId. |
503 | { "error": "Recommendations temporarily unavailable" } | The backend is briefly unreachable. Hide the block and retry on the next page view. |
200 | { "strategy": "popular", "results": [] } | The source has no searchable index yet (e.g. the first import is still running). |
In every case the correct client behavior is the same: if you do not get a
200 with a non-empty results array, do not show the block.
CORS error. Your site's origin is not on the instance's domain allowlist. Add it under Instance → Domains. An empty allowlist allows all origins.
Always Product not found. You are not passing the feed identifier.
shopItemId must be exactly the shopItemId from your products XML feed —
the same value the search API returns as externalId. It is not the
product URL, database ID, or productCode.
strategy is always popular. The recommender cannot find similar or
same-category products — usually because the seed products have no brand
and no category in the feed, or the source has very few products. Enrich
the feed with brand and categories to unlock the stronger strategies.
Out-of-stock products appear. In-stock products rank higher, but when
the similar set is small, out-of-stock products can still show. They carry
an availabilityRankText label — badge them or filter them out client-side.