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 in the test environment.  

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 in the test environment. 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_tokenFunction :

    • 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_apiFunction :

    • 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.