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:

  1. Fetches organic user top picks
  2. Fetches organic cart toppers (with cart context)
  3. Gets frequently bought together products for a specific product
  4. 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 GET and require no body. Sponsored endpoints use POST and include a client_data payload 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.

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();

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_promotedtrue for paid placements, false for organic
  • product_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:

  1. Check if the store has productPlacementEnabled (from GET /api/v1/store)
  2. If enabled, gather client data and call the sponsored POST endpoint
  3. If disabled or client data is unavailable, fall back to the organic GET endpoint
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?