Ниже в демонстрационных и тестовых целях приведет код тестового клиента API CASHOFF, написанные на python версии 3. Он содержит примеры подписи, формирования заголовков и url для вызовов. Далее, примеры использования и непосредственно исходный код:

Пример использования
c = CashoffAPIClient('https://developer.cashoff.ru', 'test', 'test')

c.request('/users')
c.request('/users/add', body={'ext_id': '123456'})

c.request('/accounts/5612', method='DELETE')
c.request('/accounts', {'profile_id': 5235})

c.request('/profiles/add', body={'provider_key': 'sber'}, user_id=2512)
Исходный код
import hashlib
import hmac
import json
import time
from base64 import b64encode
from http.client import HTTPConnection, HTTPSConnection
from urllib.parse import urlencode, urlsplit, quote


class CashoffAPIClient:
    """Базовый клиент для работы с CASHOFF API через python3."""

    def __init__(self, host, login, secret, use_sign=True):
        """
        :param host: Адрес, с указанием протокола
        :param login: логин (ServiceID)
        :param secret: пароль (PrivateKey)
        :param use_sign: использовать для аутентификации подпись; В случае False будет использовано http basic auth
        """
        url_parts = urlsplit(host)
        connection_cls = HTTPConnection if url_parts.scheme == 'http' else HTTPSConnection
        self.connection = connection_cls(url_parts.hostname, url_parts.port)

        self.login = login
        self.secret = secret
        self.use_sign = use_sign

    def request(self, url, params=None, body=None, *, user_id=None, session=None, method=None):
        """
        Выполнить GET/POST запрос
        :param url: относительный путь к запросу, как в документации
        :param params: get-параметры запроса в виде dict()
        :param body: тело, если POST; автоматически конвертируется в json
        :param user_id: идентификатор юзера; подставится в заголовок co-user-id
        :param session: идентификатор сессии; подставится в заголовок co-session, будет использоваться вместо подписи
        :param method: http-метод; по умолчанию будет подставлен get/post, в зависимости от body
        :return: пара (http код, распарсеное тело ответа/текст в случае неудачи)
        """
        url = self._format_request_url(url, params)
        if method is None:
            method = 'GET' if body is None else 'POST'

        if body is not None:
            body = json.dumps(body, ensure_ascii=False)
        headers = self._make_headers(url, body, user_id, session)
        if body is not None:
            body = body.encode('utf-8')

        try:
            self.connection.request(method, url, body, headers)
        except ConnectionResetError:
            self.connection.close()
            self.connection.request(method, url, body, headers)

        response = self.connection.getresponse()
        result = response.read().decode('utf-8')
        result = result and json.loads(result)

        return response.status, result

    def _make_headers(self, url, body, user_id=None, session=None):
        """Генерирует заголовки для запроса"""
        headers = {
            'Content-type': 'application/json',
        }

        if session:
            headers['co-session'] = session
        else:
            if self.use_sign:
                headers['co-auth'] = self._make_sign(url, body, user_id)
            else:
                headers['co-auth'] = 'http_basic'
                headers['Authorization'] = self._make_http_basic()

        if user_id is not None:
            headers['co-user-id'] = str(user_id)

        return headers

    def _make_sign(self, url, body, user_id):
        timestamp = int(time.time())
        msg = (
                self.login +
                str(timestamp) +
                (str(user_id) if user_id is not None else '') +
                self._get_url_for_sign(url) +
                (body or '')
        )
        signature = hmac.new(
            self.secret.encode('utf-8'), msg.encode('utf-8'), hashlib.sha1
        )
        return f'sha1|{self.login}:{timestamp}:{signature.hexdigest()}'

    def _make_http_basic(self):
        auth_data = quote(self.login) + ':' + quote(self.secret)
        return 'Basic ' + b64encode(auth_data.encode()).decode()

    def _get_url_for_sign(self, url):
        parts = urlsplit(url)
        url_for_sign = parts.path
        if parts.query:
            url_for_sign += '?' + parts.query
        return url_for_sign

    def _format_request_url(self, url, params):
        if url.startswith('/'):
            url = url[1:]
        if params is not None:
            url += '?' + urlencode(params)
        return '/api/v2/' + url