Recommendations Flow
A walkthrough of fetching product recommendations — organic picks, cart toppers, frequently bought together, and their sponsored counterparts.
What you'll build
A recommendations integration that:
- Fetches organic user top picks
- Fetches organic cart toppers (with cart context)
- Gets frequently bought together products for a specific product
- Compares organic results with their sponsored versions
Prerequisites
- A Store UUID (staging:
e87437f2-3e35-4738-af5e-6307e368255c) - A product ID from the catalog — see the Quick Start
- For sponsored endpoints: the store must have a Surfside integration configured
- cURL or any HTTP client
Note: Organic endpoints use
GETand require no body. Sponsored endpoints usePOSTand include aclient_datapayload with browser, location, and session information.
Setup
STORE_UUID="e87437f2-3e35-4738-af5e-6307e368255c"
BASE_URL="https://ecom-api.staging.blaze.me"
const STORE_UUID = "e87437f2-3e35-4738-af5e-6307e368255c";
const BASE_URL = "https://ecom-api.staging.blaze.me";
const headers = {
"Content-Type": "application/vnd.api+json",
Accept: "application/vnd.api+json",
"X-Store": STORE_UUID,
};
Step 1: Get Organic User Top Picks
Personalized product suggestions based on user behavior and store popularity. Works with or without authentication — if a JWT is provided, results are personalized to the user.
GET /api/v1/products/recommendations/user-top-picks
cURL
curl -X GET "$BASE_URL/api/v1/products/recommendations/user-top-picks?limit=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID"
With filters:
curl -X GET "$BASE_URL/api/v1/products/recommendations/user-top-picks?limit=5&category=flower&max_price=60" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID"
JavaScript
const topPicksRes = await fetch(
`${BASE_URL}/api/v1/products/recommendations/user-top-picks?limit=5`,
{ headers }
);
const { data: topPicks, meta } = await topPicksRes.json();
console.log(`Got ${topPicks.length} top picks`);
topPicks.forEach((p) => {
console.log(` ${p.attributes.name} — $${p.attributes.price}`);
});
Response
{
"data": [
{
"id": "12345",
"type": "products",
"attributes": {
"name": "Blue Dream",
"price": 45.00,
"category": "Flower",
"brand": "Green Farms",
"is_promoted": false,
"product_placement_campaign_id": null
}
},
{
"id": "12346",
"type": "products",
"attributes": {
"name": "Sour Diesel",
"price": 38.00,
"category": "Flower",
"brand": "Pacific Greens",
"is_promoted": false,
"product_placement_campaign_id": null
}
}
]
}
All organic results have is_promoted: false and product_placement_campaign_id: null.
Step 2: Get Organic Cart Toppers
Products commonly added alongside items already in the cart. Use cart_total and excludes to tune results based on the current cart state.
GET /api/v1/products/recommendations/cart-toppers
cURL
curl -X GET "$BASE_URL/api/v1/products/recommendations/cart-toppers?limit=4&cart_total=90&excludes=PRODUCT_A_UUID,PRODUCT_B_UUID" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID"
JavaScript
// Exclude products already in the cart and pass the current cart total
const cartProductIds = ["PRODUCT_A_UUID", "PRODUCT_B_UUID"];
const cartTotal = 90;
const toppersRes = await fetch(
`${BASE_URL}/api/v1/products/recommendations/cart-toppers?limit=4&cart_total=${cartTotal}&excludes=${cartProductIds.join(",")}`,
{ headers }
);
const { data: toppers } = await toppersRes.json();
console.log(`${toppers.length} cart topper suggestions:`);
toppers.forEach((p) => {
console.log(` ${p.attributes.name} — $${p.attributes.price}`);
});
Response
{
"data": [
{
"id": "67890",
"type": "products",
"attributes": {
"name": "Rolling Papers",
"price": 5.00,
"category": "Accessories",
"is_promoted": false,
"product_placement_campaign_id": null
}
},
{
"id": "67891",
"type": "products",
"attributes": {
"name": "Grinder",
"price": 25.00,
"category": "Accessories",
"is_promoted": false,
"product_placement_campaign_id": null
}
}
]
}
Step 3: Frequently Bought Together
Get products commonly purchased alongside a specific product. This endpoint always uses POST with the ad client data payload.
POST /api/v1/products/recommendations/frequently-bought-together/{product_id}
cURL
curl -X POST "$BASE_URL/api/v1/products/recommendations/frequently-bought-together/12345?limit=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-d '{
"data": {
"type": "recommendations",
"attributes": {
"url": "https://my-store.blaze.me/products/12345",
"screen": { "height": 1080, "width": 1920 },
"navigator": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"language": "en-US"
},
"mobile": false,
"account_id": "your-surfside-account-id",
"site_id": "your-surfside-site-id",
"channel_id": "your-channel-id",
"channel_type": "WEB",
"zone_id": "zone-for-product-page",
"client_ip": "203.0.113.42",
"surfside_domain_id": null,
"session_id": "current-session-id",
"store_location_data": {
"coords": { "latitude": 34.0522, "longitude": -118.2437 },
"zip": "90001",
"city": "Los Angeles",
"country": "US",
"state": "CA"
},
"location_data": {
"zip": "90210",
"country": "US",
"city": "Beverly Hills",
"region": "CA",
"utc_offset": -7,
"timezone": "America/Los_Angeles",
"coords": { "latitude": 34.0901, "longitude": -118.4065, "accuracy": 20 }
}
}
}
}'
JavaScript
const productId = "12345";
// Build the client data payload (reusable for all sponsored endpoints)
const clientData = {
data: {
type: "recommendations",
attributes: {
url: `https://my-store.blaze.me/products/${productId}`,
screen: { height: window.screen.height, width: window.screen.width },
navigator: {
user_agent: window.navigator.userAgent,
language: window.navigator.language,
},
mobile: /Android|iPhone|iPad/i.test(navigator.userAgent),
account_id: "your-surfside-account-id",
site_id: "your-surfside-site-id",
channel_id: "your-channel-id",
channel_type: "WEB",
zone_id: "zone-for-product-page",
client_ip: "203.0.113.42",
surfside_domain_id: null,
session_id: "current-session-id",
store_location_data: {
coords: { latitude: 34.0522, longitude: -118.2437 },
zip: "90001",
city: "Los Angeles",
country: "US",
state: "CA",
},
location_data: {
zip: "90210",
country: "US",
city: "Beverly Hills",
region: "CA",
utc_offset: -7,
timezone: "America/Los_Angeles",
coords: { latitude: 34.0901, longitude: -118.4065, accuracy: 20 },
},
},
},
};
const fbtRes = await fetch(
`${BASE_URL}/api/v1/products/recommendations/frequently-bought-together/${productId}?limit=5`,
{
method: "POST",
headers,
body: JSON.stringify(clientData),
}
);
const { data: fbtProducts, meta: fbtMeta } = await fbtRes.json();
console.log(`Recommendation ID: ${fbtMeta.recommendation_id}`);
fbtProducts.forEach((p) => {
const badge = p.attributes.is_promoted ? " [Sponsored]" : "";
console.log(` ${p.attributes.name}${badge}`);
});
Response
{
"meta": {
"recommendation_id": "rec-abc-123"
},
"data": [
{
"id": "67890",
"type": "products",
"attributes": {
"name": "Rolling Papers",
"price": 5.00,
"is_promoted": true,
"product_placement_campaign_id": "campaign-xyz",
"extras": { "impression_url": "https://..." }
}
},
{
"id": "67891",
"type": "products",
"attributes": {
"name": "Grinder",
"price": 25.00,
"is_promoted": false,
"product_placement_campaign_id": null,
"extras": null
}
}
]
}
The recommendation_id in meta can be used for tracking and attribution. Products with is_promoted: true are sponsored placements.
Step 4: Compare with Sponsored Versions
When the store has ad integrations active, upgrade organic calls to their sponsored counterparts. Sponsored endpoints blend paid placements alongside organic results.
Sponsored User Top Picks
POST /api/v1/products/recommendations/sponsored-user-top-picks
curl -X POST "$BASE_URL/api/v1/products/recommendations/sponsored-user-top-picks?limit=5" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-d '{
"data": {
"type": "recommendations",
"attributes": {
"url": "https://my-store.blaze.me/",
"screen": { "height": 1080, "width": 1920 },
"navigator": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"language": "en-US"
},
"mobile": false,
"account_id": "your-surfside-account-id",
"site_id": "your-surfside-site-id",
"channel_id": "your-channel-id",
"channel_type": "WEB",
"zone_id": "zone-for-home-page",
"client_ip": "203.0.113.42",
"surfside_domain_id": null,
"session_id": "current-session-id",
"store_location_data": {
"coords": { "latitude": 34.0522, "longitude": -118.2437 },
"zip": "90001",
"city": "Los Angeles",
"country": "US",
"state": "CA"
},
"location_data": {
"zip": "90210",
"country": "US",
"city": "Beverly Hills",
"region": "CA",
"utc_offset": -7,
"timezone": "America/Los_Angeles",
"coords": { "latitude": 34.0901, "longitude": -118.4065, "accuracy": 20 }
}
}
}
}'
const sponsoredTopPicksRes = await fetch(
`${BASE_URL}/api/v1/products/recommendations/sponsored-user-top-picks?limit=5`,
{
method: "POST",
headers,
body: JSON.stringify({
...clientData,
data: {
...clientData.data,
attributes: {
...clientData.data.attributes,
url: "https://my-store.blaze.me/",
zone_id: "zone-for-home-page",
},
},
}),
}
);
const { data: sponsoredPicks } = await sponsoredTopPicksRes.json();
Sponsored Cart Toppers
POST /api/v1/products/recommendations/sponsored-cart-toppers
curl -X POST "$BASE_URL/api/v1/products/recommendations/sponsored-cart-toppers?limit=4&excludes=PRODUCT_A_UUID" \
-H "Content-Type: application/vnd.api+json" \
-H "Accept: application/vnd.api+json" \
-H "X-Store: $STORE_UUID" \
-d '{
"data": {
"type": "recommendations",
"attributes": {
"url": "https://my-store.blaze.me/cart",
"screen": { "height": 1080, "width": 1920 },
"navigator": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"language": "en-US"
},
"mobile": false,
"account_id": "your-surfside-account-id",
"site_id": "your-surfside-site-id",
"channel_id": "your-channel-id",
"channel_type": "WEB",
"zone_id": "zone-for-cart-page",
"client_ip": "203.0.113.42",
"surfside_domain_id": null,
"session_id": "current-session-id",
"store_location_data": {
"coords": { "latitude": 34.0522, "longitude": -118.2437 },
"zip": "90001",
"city": "Los Angeles",
"country": "US",
"state": "CA"
},
"location_data": {
"zip": "90210",
"country": "US",
"city": "Beverly Hills",
"region": "CA",
"utc_offset": -7,
"timezone": "America/Los_Angeles",
"coords": { "latitude": 34.0901, "longitude": -118.4065, "accuracy": 20 }
}
}
}
}'
const sponsoredToppersRes = await fetch(
`${BASE_URL}/api/v1/products/recommendations/sponsored-cart-toppers?limit=4&excludes=${cartProductIds.join(",")}`,
{
method: "POST",
headers,
body: JSON.stringify({
...clientData,
data: {
...clientData.data,
attributes: {
...clientData.data.attributes,
url: "https://my-store.blaze.me/cart",
zone_id: "zone-for-cart-page",
},
},
}),
}
);
const { data: sponsoredToppers } = await sponsoredToppersRes.json();
Distinguishing Organic vs Sponsored in the Response
Every product in the response includes fields that identify sponsorship:
function renderProducts(products) {
products.forEach((product) => {
const { name, is_promoted, product_placement_campaign_id } =
product.attributes;
if (is_promoted) {
console.log(`[Sponsored] ${name} (campaign: ${product_placement_campaign_id})`);
} else {
console.log(name);
}
});
}
// Compare organic vs sponsored
console.log("--- Organic Top Picks ---");
renderProducts(topPicks);
console.log("--- Sponsored Top Picks ---");
renderProducts(sponsoredPicks);
Key fields:
is_promoted—truefor paid placements,falsefor organicproduct_placement_campaign_id— the ad campaign ID (null for organic)extras— additional ad metadata from the placement platform (null for organic)
Choosing Organic vs Sponsored
The decision of which endpoint to call depends on the store's configuration:
- Check if the store has
productPlacementEnabled(fromGET /api/v1/store) - If enabled, gather client data and call the sponsored
POSTendpoint - If disabled or client data is unavailable, fall back to the organic
GETendpoint
async function getTopPicks(store, limit = 5) {
if (store.attributes.productPlacementEnabled) {
// Sponsored — POST with client data
const res = await fetch(
`${BASE_URL}/api/v1/products/recommendations/sponsored-user-top-picks?limit=${limit}`,
{
method: "POST",
headers,
body: JSON.stringify(clientData),
}
);
return res.json();
}
// Organic fallback — simple GET
const res = await fetch(
`${BASE_URL}/api/v1/products/recommendations/user-top-picks?limit=${limit}`,
{ headers }
);
return res.json();
}
Complete Flow Summary
GET /api/v1/products/recommendations/user-top-picks → Organic personalized picks
GET /api/v1/products/recommendations/cart-toppers → Organic cart add-ons
POST /api/v1/products/recommendations/frequently-bought-together/{product_id} → FBT (always POST)
POST /api/v1/products/recommendations/sponsored-user-top-picks → Sponsored personalized picks
POST /api/v1/products/recommendations/sponsored-cart-toppers → Sponsored cart add-ons
What's Next?
- Add items to cart → see the Cart & Checkout Flow example
- Full ads reference → see the Ads & Recommendations guide
- Product catalog → see the Products Catalog guide