Migrate to the new MAPI

Background

We are moving towards using API Gateway for MAPI to replace our current custom service setup. The current MAPI is now considered deprecated and will be discontinued by 02.01.2025.

Here are the primary reasons for this change:

  1. Enhanced Performance and Stability: The move aims to boost system performance and provide a more reliable operational environment.
  2. Strengthened Security: By adopting API Gateway and using OAuth2, we aim to improve security measures, reducing risks and better protecting our data.
  3. Reduced Maintenance: The new setup is expected to require less maintenance, making operations more efficient.
  4. Improved Control Over Request Rates: This change will allow for better management of traffic, helping to handle request rates more effectively.
  5. Better Reporting: The transition will enhance reporting capabilities, providing clearer insights and analytics.

Migration

Authentication

The current version of MAPI authenticates requests using an API key in the header. However, the new MAPI will shift to an OAuth2 flow for authentication.

This change requires clients to first obtain a JWT access token using an ID and secret. This token will then need to be included in all subsequent calls to MAPI. 

Authentication URL's

Token expiration

The access token is valid for 3600 seconds. You can inspect it to see details like when it was issued and when it will expire.

Request URL changes

In the Management API (MAPI), the structure of request URLs is composed of three distinct parts.

To illustrate, let's use the URL responsible for retrieving a product from PIM based on its ID:

Example URL (Production environment):

https://mapi.bluestonepim.com/pim/products/id

The breakdown of this URL is as follows:

PartExample
Hostnamemapi.bluestonepim.com
Service pathpim
Resource pathproducts/{id}

Hostname

The hostname will be changed in the following way:

Service Path

The service path specifies the service to which the request is directed.

The following services will get new paths:

ServiceOld pathNew path
Bluestone MediaBankmediamedia-bank
bs-core-searchcore-searchsearch
CMScms-contextcms

Resource Path

This section represents the exact resource within the chosen service. During this migration, resource paths will remain consistent and will not be altered.

Throttling and Rate Limits

In order to ensure high performance and maintain a fair usage policy, the new Management API (MAPI) incorporate throttling mechanisms. These mechanisms control the rate at which API requests can be made, ensuring that the infrastructure remains responsive and available to all users.

The exact limits applicable to your integrations are stipulated in the contract agreement with Bluestone PIM.

The limits apply to all clients combined within a customer organization.  

Limits 

  • Rate Limit: A predefined number of requests are allowed per second (RPS). For instance, if the rate limit is set to 10 RPS, this means you can make up to 10 requests in one second.
  • Burst Limit: Beyond the steady rate limit we support a burst of up to 10 additional RPS. This provides flexibility for scenarios where short-term, higher request volumes might be needed. For example, if you do 0 requests one second, you can do 20 requests the next second. If you do 9 requests one second, you can do 11 requests the next second and so on. 

Handling Limit Exceedance

Should you exceed the set rate limits you will receive an HTTP 429 response, indicating "Too Many Requests". 

It's essential to implement a backoff strategy in your integration. A backoff strategy typically involves:

    1. Detecting when a 429 status code is received.
    2. Waiting for a predetermined duration before making the next request.
    3. Gradually increasing the wait time if subsequent requests also result in a 429 response.

This approach not only helps in complying with the rate limits but also ensures that your application remains resilient and adaptive to varying API conditions.

In conclusion, while throttling might seem like a constraint, it's a necessary measure to ensure equitable access, maintain service performance, and promote good API consumer habits. Make sure to monitor your API request patterns and adjust them as needed to fit within the provided limits. If you find that this is not possible, please contact Bluestone PIM about increasing the limits.

Examples

Basic

This Python example shows how to get an access token and fetch all catalogs in your organization.  

import requests
import json
 
# Constants
AUTH_URL = 'https://idp.test.bluestonepim.com/op/token'
CLIENT_ID = 'YOUR_ID'
CLIENT_SECRET = 'YOUR_SECRET'
BASE_URL = "https://api.test.bluestonepim.com"
 
 
def get_token(auth_url, client_id, client_secret):
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    data = {
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret,
    }
    response = requests.post(auth_url, headers=headers, data=data)
    auth_json = response.json()
    return auth_json['access_token']
 
def request_api(base_url, endpoint, method, access_token, payload=None):
    url = f"{base_url}/{endpoint}"
    headers = {
        'context-fallback': 'true',
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {access_token}'
    }
    response = requests.request(method, url, headers=headers, json=payload)
    return response.json()
 
 
# Usage
access_token = get_token(AUTH_URL, CLIENT_ID, CLIENT_SECRET)
endpoint = "pim/catalogs"
method = "GET"
response_json = request_api(BASE_URL, endpoint, method, access_token)
print(response_json)

Advanced 

This Python example also shows how to get an access token and fetch all catalogs in your organization. The script is structured to handle common error conditions that might occur when interacting with an API, like network issues, server errors, or authentication problems, and employs retry logic with backoff to increase robustness and resilience in the face of transient errors. There is a more detailed explanation below the code.

import requests
import time
from datetime import datetime, timedelta
 
# Constants
AUTH_URL = 'https://idp.test.bluestonepim.com/op/token'
CLIENT_ID = 'YOUR_ID'
CLIENT_SECRET = 'YOUR_SECRET'
BASE_URL = "https://api.test.bluestonepim.com"
TIMEOUT = 10  # Request timeout in seconds
RETRY_STATUSES = {429, 500, 502, 503, 504} # HTTP response statuses to retry
TOKEN_RETRIES = 3  # Number of retries for fetching the token
 
# Global variable to store token information
token_info = None
 
def get_token():
    global token_info
    # If we have a token and it's not expired, reuse it
    if token_info and token_info['expiry_time'] > datetime.now():
        return token_info['access_token']
 
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    data = {
        'grant_type': 'client_credentials',
        'client_id': CLIENT_ID,
        'client_secret': CLIENT_SECRET,
    }
    for retry in range(TOKEN_RETRIES):
        try:
            response = requests.post(AUTH_URL, headers=headers, data=data, timeout=TIMEOUT)
            if response.status_code in RETRY_STATUSES and retry < TOKEN_RETRIES - 1:
                print(f"Received {response.status_code} while fetching token, waiting 3 seconds before retrying...")
                time.sleep(3)
                continue
            response.raise_for_status()
            auth_json = response.json()
            expiry_time = datetime.now() + timedelta(seconds=auth_json['expires_in'])
            token_info = {'access_token': auth_json['access_token'], 'expiry_time': expiry_time}
            return token_info['access_token']
        except requests.exceptions.RequestException as e:
            print(f"An error occurred while fetching token: {e}")
            if retry >= TOKEN_RETRIES - 1:
                print("Max retries to get token exceeded.")
                raise e
 
 
def request_api(endpoint, method, payload=None, retries=5, backoff_factor=2):
    url = f"{BASE_URL}/{endpoint}"
     
    access_token = get_token()  
     
    for retry in range(retries):
         
        headers = {
            'context-fallback': 'true',
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {access_token}'
        }
        try:
            response = requests.request(method, url, headers=headers, json=payload, timeout=TIMEOUT)
            # Only retry for 429 Too Many Requests or certain 5xx server errors
            if response.status_code in RETRY_STATUSES and retry < retries - 1:
                print(f"Received {response.status_code}, retrying...")
                 
                # Apply exponential backoff
                time.sleep(backoff_factor ** retry)
                continue
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx and 5xx)
            return response.json()
        except requests.exceptions.RequestException as e:           
            print(f"An error occurred: {e}")
            print("Method parameters", endpoint, method, payload)
            raise e
 
# Usage
endpoint = "pim/catalogs"
method = "GET"
response_json = request_api(endpoint, method)
print(response_json)

Here's a breakdown of what each part of the code does:

  1. Constants and Global Variable:

    • The script begins by defining several constants and a global variable to hold the authentication token information. The constants include URLs, client credentials, a request timeout value, a set of HTTP status codes to retry on, and a number of retries for fetching the token.
  2. get_token Function:

    • This function is responsible for obtaining an authentication token from the authentication server.
    • It first checks if there's an existing token that hasn't expired yet, reusing it if possible to avoid unnecessary requests.
    • If no valid token is available, it makes a POST request to the authentication server, retrying up to TOKEN_RETRIES times if certain error conditions are encountered (like a timeout or one of the specified retryable HTTP status codes).
    • A simple 3-second wait is introduced before retrying to fetch the token if a retryable error occurs.
    • If a token is successfully obtained, the function updates the global token_info variable with the new token and its expiry time, then returns the token.
    • If the maximum number of retries is exceeded or other request errors occur, it prints an error message to the console and raises the exception to halt execution.
  3. request_api Function:

    • This function is designed to make a request to a specified endpoint of the API.
    • It first calls get_token to ensure it has a valid authentication token.
    • It then attempts to make the API request, retrying up to a specified number of times (retries) if a retryable error condition occurs (like a 429 Too Many Requests or certain 500-level server errors).
    • An exponential backoff strategy is employed to wait increasingly longer amounts of time before each retry, helping to alleviate load on the server and increase the likelihood of a successful request on subsequent retries.
    • If a request error occurs, it prints an error message along with the method parameters to the console for debugging purposes, and then raises the exception to halt execution.
  4. Usage:

    • Finally, the script demonstrates how to use the request_api function to make a GET request to a specific endpoint of the API.
    • It prints the response json to the console.