Solutions / Ecommerce / Sponsored products / Guides

Use the RMP first strategy if your retail media platform (RMP) only returns product identifiers, not full product details. This guide explains how the strategy works, its requirements, and how to implement it.

How the RMP first strategy works

RMP first strategy

  1. User performs a search (for example, “running shoes”).
  2. Your app queries the RMP for a list of sponsored product IDs.
  3. Your app sends two queries to Algolia:

    • One query fetches product records for the sponsored items.
    • The other query fetches regular search results.
  4. Your app merges these results, removes duplicates, and injects sponsored items at specific positions.
  5. The combined list of regular and sponsored results is sent to the frontend for display.

Requirements

To effectively implement the RMP first strategy, the RMP’s API must return unique product identifiers. These are ideally objectIDs that align with your Algolia index. If an Algolia objectID isn’t available, your identifier must reliably select the appropriate Algolia records.

Pros

  • Consistent product information. Because the product details for sponsored and regular results come from Algolia, users always see consistent and up-to-date product information.
  • Simpler deduplication. Since all product data comes from Algolia, merging and deduplication is straightforward.

Cons

  • Slower than the parallel requests strategy. Because the Algolia multi-query must wait until the RMP call finishes, the response time is slower than the parallel request strategy.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import algoliasearch from 'algoliasearch';

// Constants
const RETAIL_MEDIA_TIMEOUT_MS = 500;
const RETAIL_MEDIA_API_ENDPOINT = 'https://api.retailmediaplaform.com/search/v1/products/';

const SPONSORED_PRODUCT_POSITIONS = [0, 1, 8, 12];

const ALGOLIA_INDEX_NAME = 'prod_products';
const ALGOLIA_APP_ID = '';
const ALGOLIA_API_KEY = '';

// Algolia setup
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_API_KEY);
const index = client.initIndex(ALGOLIA_INDEX_NAME);

async function fetchRetailMediaWithTimeout(searchQuery, filters) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), RETAIL_MEDIA_TIMEOUT_MS);

  try {
    const { statusCode, body } = await request(RETAIL_MEDIA_API_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      signal: controller.signal,
      body: JSON.stringify({
        keyword: searchQuery,
        filters: filters
      })
    });

    clearTimeout(timeout);

    if (statusCode === 200) {
      return await body.json();
    }
    throw new Error(`Request failed with status ${statusCode}`);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('Retail Media Platform request timeout');
    } else {
      console.error('Retail Media Platform request failed:', error);
    }
    return null;
  }
}

/**
 * Injects sponsored products into regular hits at specific positions defined by SPONSORED_PRODUCT_POSITIONS.
 * Removes any duplicate products that exist in both regular and sponsored hits.
 * @param {Array} regularHits - The array of regular product hits from Algolia
 * @param {Array} sponsoredHits - The array of sponsored product hits from RMP
 * @returns {Array} Combined array of regular and sponsored hits with sponsored products at specified positions
 */
function injectSponsoredProducts(regularHits, sponsoredHits) {
  if (!sponsoredHits?.length) return regularHits;

  // Remove duplicates
  const sponsoredIds = new Set(sponsoredHits.map(hit => hit.objectID));
  const finalHits = regularHits.filter(hit => !sponsoredIds.has(hit.objectID));

  // Insert sponsored products
  SPONSORED_PRODUCT_POSITIONS.forEach((position, index) => {
    if (index < sponsoredHits.length) {
      const sponsoredHit = { ...sponsoredHits[index], sponsored: true };
      const insertPosition = Math.min(position, finalHits.length);
      finalHits.splice(insertPosition, 0, sponsoredHit);
    }
  });

  return finalHits;
}

/**
* Performs sequential search requests, first to RMP, then to Algolia using multiple queries
* Uses RMP results to fetch matching products from Algolia, then combines them with regular results.
* Falls back to regular Algolia search if multiple queries fail.
* @param {string} searchQuery - The search term to find products
* @param {Object|string} filters - Filtering criteria for both searches
* @returns {Promise<Array>} Combined array of regular and sponsored hits
*/
async function searchProducts(searchQuery, filters) {
    // First, get sponsored products from RMP
    const retailMediaResult = await fetchRetailMediaWithTimeout(searchQuery, filters);
    const sponsoredProductIds = retailMediaResult?.hits?.map(hit => hit.objectID) || [];
  
    // Prepare queries for Algolia
    const queries = [
      // First query: get records associated with sponsored products
      {
        indexName: ALGOLIA_INDEX_NAME,
        query: searchQuery,
        params: {
          filters: sponsoredProductIds.length 
            ? `(${filters}) AND (objectID:${sponsoredProductIds.join(' OR objectID:')})`
            : filters,
          analytics: false, // Turn off analytics
          clickAnalytics: false, // Turn off click analytics
          getRankingInfo: false, // Turn off ranking information
          responseFields: ["hits"], // Optimize response payload size
          ruleContexts: ["retail-sponsored-products"]
          hitsPerPage: sponsoredProductIds.length || 1  // If no sponsored products, minimize response
        }
      },
      // Second query: get regular results
      {
        indexName: ALGOLIA_INDEX_NAME,
        query: searchQuery,
        params: {
          filters: filters,
          hitsPerPage: 48
        }
      }
    ];

  try {
    // Execute multiple queries and destructure results
    const { results: [sponsoredResults, regularResults] } = await client.multipleQueries(queries);

      // Merge results by removing hits duplicates
    return injectSponsoredProducts(regularResults.hits, sponsoredResults.hits);

  } catch (error) {
    console.error('Algolia multiple queries failed:', error);
    // Fallback to regular search if multiple queries fail
    const { hits } = await index.search(searchQuery, {
      filters: filters,
      hitsPerPage: 48
    });
    return hits;
  }
}

// Usage
const searchQuery = 'running shoes';
const filters = 'size:10 AND color:"blue"';

const results = await searchProducts(searchQuery, filters);
render(results);

How the code works

  1. Call the RMP. The fetchRetailMediaWithTimeout function requests a list of sponsored product IDs from the RMP. If the request times out, it returns null.
  2. Algolia multi-query request. Sends a multi-query to Algolia, requesting detailed information for sponsored and regular products.
  3. Merge and deduplicate. The injectSponsoredProducts function removes any duplicates between the two sets of results and inserts sponsored items at specified positions.
  4. Return merged results. The frontend receives the merged list, where sponsored products are appropriately labeled.

Before your strategy goes live, ensure you’ve considered the regulatory requirements and the influence of sponsored results on your analytics, ranking, and UI.

Did you find this page helpful?