Handle error 152 “Insufficient points”

Python version 2 or 3 using JSON with the Requests library

In version 5 of the Yandex.Direct API, points are awarded to all advertisers every 60 minutes, but each advertiser has its own schedule for awarding points that is not related to the beginning of the astronomical hour. The example shows how requests are executed if the first request returns the 152 error code.

For demonstration purposes, it uses the sleep function, which allows you to set delays for running the script. The delays are set in seconds in the delays array, so that after the first attempt no more than 5 repeat requests are sent with increasing intervals that cover the next 60 minutes after receiving the first error.

# -*- coding: utf-8 -*-
import sys
from time import sleep

import json
import requests

if sys.version_info < (3,):
    def u(x):
        try:
            return x.encode("utf8")
        except UnicodeDecodeError:
            return x
else:
    def u(x):
        if type(x) == type(b''):
            return x.decode('utf8')
        else:
            return x

# --- Settings ---
# Output debugging information
debug = True

# Array of delay intervals
delays = [360, 540, 720, 900, 1080]

# "Insufficient points" error
notEnoughUnitsError = "152"

# --- Input data ---
# Address of the Campaigns service for sending JSON requests (case-sensitive)
CampaignsURL = 'https://api.direct.yandex.com/json/v5/campaigns'

# OAuth token of the user that requests will be made on behalf of
token = 'TOKEN'

# Login of an advertising agency client
# Required parameter if requests are made on behalf of an advertising agency
clientLogin = 'CLIENT_LOGIN'

# --- Preparing the request ---
# Creating HTTP request headers
headers = {
    "Authorization": "Bearer " + token,     # OAuth token. The word Bearer must be used
    "Client-Login": clientLogin,            # Login of the advertising agency client
    "Accept-Language": "en",                # Language for response messages
}

# Создание тела запроса
body = {
    "method": "get",                        # Method to use
    "params": {
        "SelectionCriteria": {},            # Criteria for filtering campaigns. To get all campaigns, leave it emtpy
        "FieldNames": ["Id", "Name"]        # Names of parameters to get
    }
}

# Transforming input data to JSON
jsonBody = json.dumps(body, ensure_ascii=False).encode('utf8')

# --- Performing tasks ---
# Starting the request execution loop. If the first request completed successfully, i.e. it did not return error 152, 
# a list of campaigns is returned.
# If the first request failed with this error, repeat requests are performed
# with the delays that are set in the delays array.
for delay in delays:
    try:
        # Executing the request and getting the result
        result = requests.post(CampaignsURL, jsonBody, headers=headers)

        # Debugging information
        if debug:
            print("Request headers: {}".format(result.request.headers))
            print("Request: {}".format(u(result.request.body)))
            print("Response headers: {}".format(result.headers))
            print("Response: {}".format(u(result.text)))
            print("\n")

        # Request handling
        if result.status_code == 200:
            # If HTTP 200 was received, the response message body is processed

            # Output the RequestId and information about points
            print("RequestId for the request: {}".format(result.headers.get("RequestId", False)))
            print("Information about points: {}".format(result.headers.get("Units", False)))

            # If the output data doesn't contain the first error key, it means the request was processed successfully
            if not result.json().get("error", False):
                # Output the list of campaigns
                for campaign in result.json()["result"]["Campaigns"]:
                    print("Ad campaign: {} №{}".format(u(campaign['Name']), campaign['Id']))

                # If the response contains the LimitedBy parameter, it means not all available objects were output.
                if result.json()['result'].get('LimitedBy', False):
                    # In this case, send additional requests to get all objects.
                    # Details on paginated selections  - https://tech.yandex.ru/direct/doc/dg/best-practice/get-docpage/#page
                    print("Not all objects received.")

                # Forced exit from loop
                break

            # Processing errors for a request to the Yandex.Direct API server
            elif result.json().get("error", False):
                print("Error connecting to the Yandex.Direct API server.")
                print("Error code: {}".format(result.json()["error"]["error_code"]))
                print("Error description: {}".format(u(result.json()["error"]["error_detail"])))
                if result.json()['error'].get('error_code', 0) == notEnoughUnitsError:
                    # Insufficient points to execute the request
                    print("Repeat request in {} seconds".format(delay))
                    # Delay before the next request
                    sleep(delay)

        # Handling other errors
        else:
            print("An error occurred when connecting to the Yandex.Direct API server.")
            print("HTTP error code: {}".format(u(result.status_code)))
            # Describe actions to take when an HTTP request error occurs

    # Error handling when the connection to the Yandex.Direct API server failed
    except ConnectionError:
        # In this case, we recommend repeating the request again later
        print("An error occurred when connecting to the API server.")
        # Forced exit from loop
        break


    # If any other error occurred
    except:
        # In this case, we recommend analyzing the application's actions
        print("Unexpected error occurred.")
        # Forced exit from loop
        break