Products and recommendations
GET /cx/v1/products/top/{range}
Section titled “GET /cx/v1/products/top/{range}”Bestsellers for a period, ranked by revenue. {range} takes any range token.
| Param | Default | Notes |
|---|---|---|
groupBy |
product |
product or category |
category |
Filter to one category | |
limit |
20 | 1 to 100 |
rep |
A sales rep code: only that rep’s sales (for rep portals) |
Publishable keys receive rank, units and orders, with an aggregation floor: rows under 5 orders are left out. Secret keys also receive revenue and aov, with no floor.
curl "https://api.plunk.io/cx/v1/products/top/this_month?limit=3" \ -H "Authorization: Bearer sk_live_••••"const res = await fetch( 'https://api.plunk.io/cx/v1/products/top/this_month?limit=8&category=Sofas', { headers: { Authorization: 'Bearer pk_live_••••' } },);const { items } = await res.json(); // rank, id, title, category, units, ordersres = requests.get( "https://api.plunk.io/cx/v1/products/top/this_month", params={"limit": 3, "groupBy": "product"}, headers={"Authorization": f"Bearer {os.environ['PLUNK_SECRET_KEY']}"},){ "range": "this_month", "groupBy": "product", "keyClass": "secret", "count": 3, "items": [ { "rank": 1, "id": "SF-1042", "title": "Harbor Sofa", "category": "Sofas", "units": 41, "orders": 38, "revenue": 61250.00, "aov": 1611.84 } ]}GET /cx/v1/products/{id}
Section titled “GET /cx/v1/products/{id}”The public product card, safe for a storefront. Never includes cost.
{ "id": "SF-1042", "title": "Harbor Sofa", "category": "Sofas", "price": 1899.00, "salePrice": null, "image": "https://…", "link": "https://…", "availability": "in stock", "brand": "…"}An unknown id returns 404 product_not_found.
GET /cx/v1/products/search
Section titled “GET /cx/v1/products/search”Semantic search. It matches on meaning rather than keywords, so “something for a small entryway” finds narrow console tables even when none of those words appear in a title. Results are then re-ranked by a blend of relevance, units sold and conversion rate, applied only among products that already matched.
| Param | Default | Notes |
|---|---|---|
q |
Required. What to search for, in plain language. | |
filter |
Optional keyword filter applied on top of the semantic match. | |
rank |
blended |
similarity returns pure semantic order, with no commercial weighting. Useful for comparing the two. |
limit |
20 | 1 to 100 |
curl "https://api.plunk.io/cx/v1/products/search?q=dresser&limit=3" \ -H "Authorization: Bearer pk_live_••••"const q = encodeURIComponent('something for a small entryway');const res = await fetch(`https://api.plunk.io/cx/v1/products/search?q=${q}&limit=12`, { headers: { Authorization: 'Bearer pk_live_••••' },});const { items } = await res.json();res = requests.get( "https://api.plunk.io/cx/v1/products/search", params={"q": "dresser", "limit": 3}, headers={"Authorization": "Bearer pk_live_••••"},){ "query": "dresser", "ranking": "blended", "count": 3, "items": [ { "rank": 1, "id": "…", "title": "Carino Dresser - Frost", "category": "Bedroom", "link": "https://…", "imageLink": "https://…", "price": 1299.00, "availability": "in stock", "unitsSold": 24 } ]}GET /cx/v1/products/trending
Section titled “GET /cx/v1/products/trending”What’s getting attention, which is a different question from /products/top/{range}. That one ranks by revenue actually booked. This one ranks by views, engagements, conversions or units from your site tracking.
| Param | Default | Notes |
|---|---|---|
range |
this_month |
Any range token. The default is deliberate: scoped to all_time, “trending” would mean “best ever” rather than “moving now”. |
sort |
sales_quantity |
Or views, engagements, conversions. |
filter |
Optional keyword filter. | |
limit |
20 | 1 to 100 |
{ "range": "this_month", "sort": "views", "count": 20, "items": [ { "rank": 1, "id": "…", "title": "Pacifico Dining Chair", "category": "Seating", "link": "https://…", "imageLink": "https://…", "price": 449.00, "availability": "in stock" } ]}Recommendations
Section titled “Recommendations”“Customers also bought,” computed from real purchase history. Two endpoints: one counts co-occurrence live, the other reads a model rebuilt nightly. Scope recommendations:read covers both. They return rank and co-purchase evidence, never revenue.
GET /cx/v1/recommendations
Section titled “GET /cx/v1/recommendations”| Param | Default | Notes |
|---|---|---|
product |
With it: products most often in the same order as this one. Without it: top sellers (a popular fallback). | |
limit |
10 | 1 to 50 |
{ "basis": "co_purchase", "product": "SF-1042", "count": 10, "items": [ { "rank": 1, "id": "SF-1042-OTT", "title": "Harbor Ottoman", "category": "Ottomans", "coPurchasedOrders": 19 } ]}GET /cx/v1/recommendations/model
Section titled “GET /cx/v1/recommendations/model”The same question answered from a model rebuilt nightly instead of counted live. Prefer this one. The endpoint above counts products in the same order. This one counts distinct accounts that bought both over 24 months, weights each pair by how much evidence supports it, and keeps freight, fabric surcharges and card fees out of the results. Both are kept so existing integrations don’t change under you.
| Param | Default | Notes |
|---|---|---|
product |
What is bought alongside this product. | |
account |
What this account should buy next, excluding what it already owns. | |
contact |
A person id. Resolves to the account or accounts that contact belongs to and recommends across all of them. Pass exactly one of product, account or contact. |
|
limit |
10 | 1 to 50 |
curl "https://api.plunk.io/cx/v1/recommendations/model?product=SF-1042&limit=3" \ -H "Authorization: Bearer pk_live_••••"const res = await fetch( 'https://api.plunk.io/cx/v1/recommendations/model?product=SF-1042&limit=4', { headers: { Authorization: 'Bearer pk_live_••••' } },);const { items } = await res.json();// Show the SKU with the title: mirror halves of a sectional can share a title.res = requests.get( "https://api.plunk.io/cx/v1/recommendations/model", params={"account": "D-1042", "limit": 10}, headers={"Authorization": f"Bearer {os.environ['PLUNK_SECRET_KEY']}"},){ "basis": "co_purchase_model", "product": "SF-1042", "account": null, "count": 3, "items": [ { "rank": 1, "id": "SF-1043", "title": "Harbor Loveseat", "category": "Seating", "score": 0.594166, "accounts": 22 } ]}scoreruns from 0 to 1; higher is stronger.accountsis how many accounts bought both.- An empty
itemsarray is a real answer, not an error. The product is new, has too little purchase history to be confident, or the model hasn’t been built for that catalog yet. - Sectional and modular goods often pair a product with its own mirror half (a left-facing piece with its right-facing twin), so two results can share a title while differing by SKU. That is the strongest signal in the data, not a duplicate. Render the SKU, not the title alone.
- On
?product,scorecompares pairs across the whole catalog. On?accountit is scaled to the strongest hit in that response, so it ranks items within one answer and isn’t comparable between accounts.
Related
Section titled “Related”- Catalog and availability
- Date ranges
- Dealers for rep and dealer portals
Still stuck? Contact support and include the job or page you were on.