Pages

The Product API requires the Frosmo Recommendations add-on for the Frosmo Control Panel. To get the add-on, if you do not already have it, contact your Frosmo representative or sales@frosmo.com.

The Product API allows you to retrieve product data from the Frosmo back end. You can retrieve the full data for one or more products based on product IDs. To get the IDs, use the Graniitti API.

For instructions on how to develop product recommendations in the Frosmo Platform, see the Frosmo Recommendations guide.

Product API vs. Graniitti API

You can retrieve product data through both the Product API and the Graniitti API. Here's how the two APIs differ:

  • The Product API returns the full data for a product. The Graniitti API only returns the main details for a product, namely its ID, name, and type.
  • The Product API does not require authentication. The Graniitti API uses token-based authentication.
  • The Product API requires the ID of each product whose data to return. The Graniitti API can retrieve all products for a site (no product IDs required) or a single product based on its ID.
  • The Product API only cares about product data. The Graniitti API can additionally retrieve and manage relevant products for a modification.

In a nutshell:

  • If you want to retrieve the full data for a product, use the Product API.
  • If you want to find out the IDs of products, or if you want to map products to modifications, use the Graniitti API.

For more information about the Graniitti API, see the Graniitti API guide.

Product API vs. the data layer

The Product API returns the exact same product data you send to the Frosmo back end through the Frosmo data layer. However, the product object properties and structure differ between the data layer and the API:

  • Data layer uses the frosmoProduct prefix for all product object properties. For example: frosmoProductId and frosmoProductName
  • Product API omits the prefix in the returned product data. For example: id and name
  • frosmoProductCategory in the data layer maps to type in the Product API.
  • Data layer uses a flat object structure. The Product API nests all product data under the attributes property, except id, name, and type, which are returned in the product object root.

For more information about the data layer and its product object model, see Frosmo data layer.

Requests

URL

The URL for Product API requests is https://<platform_instance>/productApi, where <platform_instance> is the domain name of your Frosmo Platform instance.

To get the URL for your Frosmo Platform instance:

  1. In the Frosmo Control Panel, select Utilities > Frosmo APIs.
  2. In the Product API section, copy the URL.

    Base URL for Product API requests

Authentication

The Product API does not require authentication.

HTTP methods

The Product API supports the following standard HTTP methods:

  • GET
  • POST

The only difference between the two is that with GET you provide the product IDs in the request URL, while with POST you provide the product IDs in the request body. If your GET request URL exceeds the maximum URL length, use a POST request instead.

You can also request product data in batches. For a JavaScript example of a batch request, see Example: Requesting product data in batches.

Usage

To retrieve product data using the GET method, make the following request:

GET
GET https://<platform_instance>/productApi?method=fetch&
    origin=<site_origin>&
    ids=["<product_1_id>","<product_2_id>","<product_n_id>"]

To retrieve product data using the POST method, make the following request:

POST
POST https://<platform_instance>/productApi?method=fetch&
     origin=<site_origin>

# REQUEST BODY

[
  "<product_1_id>",
  "<product_2_id>",
  "<product_n_id>"
]

Parameters

Table: Query parameters for Product API requests

ParameterDescriptionTypeRoleExample
method

Request type.

The possible values are:

  • fetch to retrieve product data using GET or POST
StringRequired
method=fetch
origin

Site origin.

To find out your site's origin, see Getting your site origin.

StringRequired/Optional
origin=shop_company_com
ids

IDs of the products whose data you want to retrieve.

The array must not be empty ([]).

The API ignores empty values ([""]).

This query parameter is only required for GET requests. POST requests pass the array of IDs in the request body.

Array of stringsRequired/Optional

Retrieve the data for one product:

ids=["123"]

Retrieve the data for two products:

ids=["123","456"]

Examples

Example: Retrieve the product data for three products (GET)
GET https://<platform_instance>/productApi?method=fetch&
    origin=shop_company_com&
    ids=["123","456","789"]
Example: Retrieve the product data for three products (POST)
POST https://<platform_instance>/productApi?method=fetch&
     origin=shop_company_com

# REQUEST BODY

["123","456","789"]

Responses

Success

On success, the Product API returns the requested data in the JSON format. The response status code is 200.

The response body is an object that contains the data array. Each item in the data array is an object that stores the data for a single product.

{
  "data": [
    {<product 1 data>},
    {<product 2 data>},
    ...
    {<product n data>}
  ]
}

Each product object contains the full set of product data tracked for the product on the site. The main product data fields (id, type, name, created_atupdated_at) are direct properties of the object, while all other product data fields are stored as properties of the attributes object.

# PRODUCT OBJECT

{
  "data": [
    {
      "id": "123",
      "type": "Books",
      "name": "A Developer's Guide to Everything",
      "created_at": "2018-11-08T10:45:15Z",
      "updated_at": "2018-11-08T10:51:05Z",
      "attributes": {
        "image": "https://shop.company.com/products/123456/images/123456.png",
        "price": "29.99",
        "url": "https://shop.company.com/products/123456"
      }
    }
  ]
}

Note the following about the returned product data:

  • Product data fields are always returned as strings. For example, while price is a decimal number, it's always returned as a string value.
  • Product data fields data and promotionLabel are flattened on to the attributes object. The original fields are also retained as properties of the attributes object. The order in which the fields are flattened is random, so if both fields contain objects with the same property, the field that gets flattened last will overwrite the property flattened from the first field.

    # data was flattened first
    
    "attributes": {
      "data": "{\"onSale\":true,\"discountPrice\":12}",
      "promotionLabel": "{\"discountPrice\":13,\"color\":\"red\"}",
      "onSale": true,
      "discountPrice": 13,
      "color": "red"
    }
    
    # promotionLabel was flattened first
    
    "attributes": {
      "data": "{\"onSale\":true,\"discountPrice\":12}",
      "promotionLabel": "{\"discountPrice\":13,\"color\":\"red\"}",
      "discountPrice": 12,
      "color": "red",
      "onSale": true
    }

    The promotionLabel field is intended for promotion-related label strings. Do not use it for stringified objects. If you need to store extra product data beyond the standard product data fields, use the data field instead. For more information, see frosmoProductData and frosmoProductPromotionLabel in Tracking products with the data layer.

Error

On an error, the Product API returns the response status code 400 and an error message.

Table: Product API error messages

Error messageDescriptionSolution
parameter 'method' missingThe method query parameter is missing from the request URL or its value is empty.Check that the method parameter is properly defined.
invalid method '<parameter_value>'The value of the method query parameter, <parameter_value>, is invalid.Provide fetch as the method parameter value.
parameter 'origin' missingThe origin query parameter is missing from the request URL or its value is empty.Check that the origin parameter is properly defined.
invalid origin-parameter '<parameter_value>'The value of the origin query parameter, <parameter_value>, is invalid.Provide a valid site origin as the origin parameter value. To find out your site's origin, see Getting your site origin.
parameter 'ids' missingThe ids query parameter is missing from the request URL or its value is empty.Check that the ids parameter is properly defined.
parameter 'ids' contains invalid JSONThe value of the ids query parameter is not a valid JSON array of strings.

Provide a valid JSON array of strings as the ids parameter value. For example:

ids=["123","456","789"]
must provide at least one product idThe value of the ids query parameter is an empty array.

Provide at least one ID in the ids parameter. For example:

ids=["123"]

Example: Requesting product data in batches

The following JavaScript example shows you how to request product data from the Product API in batches. The example defines the asyncMap function, which iterates through a list of product IDs and chunks the IDs into a series of Product API requests.

The example requires a browser that supports ES6 (ECMAScript 2015) and the Fetch API. For more information about browser support, see the ES6 compatibility table (GitHub) and Fetch API (MDN).

You can run the example as is in your browser console, since the code only simulates a Product API request. In actual usage, omit the fetch() function from the end, define proper values for frosmoDomain, siteOrigin, and productIds, and optionally customize the asyncMap() options.

/**
 * Similar to http://bluebirdjs.com/docs/api/promise.map.html
 *
 * @param {Array<any>} iterable An array of values (Promises).
 * @param {Function} mapper A mapper function. The function is called with fn(value, index, iterable).
 * @param {Object} [options] Optional options object.
 * @param {number} [options.chunkSize] Optional chunk size for splitting the array into smaller chunks. The default is 0 (no splitting).
 * @param {number} [options.concurrency] Optional concurrency for the maximum number of concurrent Promises running at the same time. The default is 8.
 * @returns {Promise} Returns a Promise that is either resolved or rejected.
 */
function asyncMap(iterable, mapper, options = { chunkSize: 0, concurrency: 8, })  {
    if (!Array.isArray(iterable)) {
        return Promise.reject(new TypeError('Invalid argument type. Expected "iterable" to be an array.'));
    }

    if (typeof mapper !== 'function') {
        return Promise.reject(new TypeError('Invalid argument type. Expected "mapper" to be a function.'));
    }

    if (Object(options) !== options) {
        return Promise.reject(new TypeError('Invalid argument type. Expected "options" to be an object.'));
    }

    const state = {
        iterable,
        isChunked: false,
        chunkSize: 0,
        concurrency: 8,
        values: [],
    };

    if (options.chunkSize !== undefined && (typeof options.chunkSize !== 'number' || options.chunkSize < 0)) {
        return Promise.reject(new TypeError('Invalid argument type. Expected "options.chunkSize" to be a number greater than zero.'));
    } else {
        state.chunkSize = options.chunkSize;
        state.isChunked = options.chunkSize > 0;
    }

    if (options.concurrency !== undefined && (typeof options.concurrency !== 'number' || options.concurrency < 1)) {
        return Promise.reject(new TypeError('Invalid argument type. Expected "options.concurrency" to be a number greater than zero.'));
    }
    state.concurrency = options.concurrency;

    if (state.isChunked) {
        state.iterable = [];
        for (let i = 0; i < iterable.length; i += state.chunkSize) {
            state.iterable.push(iterable.slice(i, i + state.chunkSize));
        }
    }

    state.concurrency = state.concurrency < state.iterable.length ? state.concurrency : state.iterable.length;
    state.remaining = state.iterable.length;

    const promise = new Promise((resolve, reject) => {
        for (let i = 0; i < state.concurrency; i += 1) {
            next(i, resolve, reject);
        }
    });

    return promise.then(values => state.isChunked ? [].concat(...values) : values);

    /**
     * Iterate over all the iterable values by the current index.
     *
     * @private
     * @param {number} currIndex Current index value of the iterable.
     * @param {Function} resolve Resolution function. Called when all values in the iterable have been successfully resolved.
     * @param {Function} resolve Rejection function. Called when the iterable value is rejected or an unexpected error occurs.
     * @returns {void}
     */
    function next(currIndex, resolve, reject) {
        // Ensure the iterable value is "promisified".
        Promise.resolve(state.iterable[currIndex])
            .then(value => mapper(value, currIndex, state.iterable))
            .then((value) =>  {
                // Updates the values array.
                state.values[currIndex] = value;

                // Resolve when all iterable values have been resolved.
                if (--state.remaining === 0) {
                    resolve(state.values);
                    return;
                }

                const nextIndex = currIndex + state.concurrency;
                if (nextIndex < state.iterable.length) {
                    // Resolve the next iterable value
                    next(nextIndex, resolve, reject);
                }
            })
            .catch(reject)
    }
}

// USAGE EXAMPLE

// Define the domain name of your Frosmo Platform instance
// and your site origin.
const frosmoDomain = 'inpref.com';
const siteOrigin = 'my_site_origin';

// Define the IDs of the products whose data to retrieve.
const productIds = [];

// Define 250 dummy product IDs for example purposes. Use valid IDs in actual usage.
for (let i = 1; i <= 250; i += 1) {
    productIds.push(String(i));
}

const opts = {
    // A maximum of 5 requests, so as not to oversaturate the Product API.
    concurrency: 5,

    // Split the array of product IDs into chunks of 50, that is,
    // into an array of 5 items, with 50 IDs in each item.
    chunkSize: 50
};

asyncMap(productIds, productIds => getProducts(productIds), opts)
    .then(res => arrayFlatten(res))
    .then(products => console.log('Successfully completed all requests', products))
    .catch(err => console.error(err));

// Get the product data for the array of product IDs.
function getProducts(productIds) {
    return fetch(`https://${frosmoDomain}/productApi?method=fetch&origin=${siteOrigin}&ids=${JSON.stringify(productIds)}`)
        .then(res => res.json())
        .then(res => res.data);
}

// Flatten an array.
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
function arrayFlatten(arr) {
    return [].concat(...arr);
}

// NOTE! The following code simulates a window.fetch() call for example purposes. Omit the code in actual usage.

// Simulate a "fetch" request.
function fetch(url) {
    console.log(`Fetching for URL: "${url}"`);

    const rand = Math.floor(Math.random() * 250);
    const res = {
        json() {
            const data = [];
            for (let i = 1; i <= rand; i += 1) {
                data.push({
                    id: Math.floor(Math.random() * 10000)
                });
            }
            return data;
        },
    };

    return new Promise(resolve => setTimeout(() => resolve(res), rand));
}
  • No labels