def METHOD_NAME(
    cls,
    method: str,
    api_endpoint: str,
    params: Optional[dict] = None,
    headers: Optional[dict] = None,
    api_key: Optional[str] = None,
) -> Union[dict, str, None]:
    log_prefix = f"[{cls.__name__}]"
    api_key = api_key or PROLIFIC_API_KEY
    if not api_key:
        raise ProlificAPIKeyError
    try:
        url = urljoin(BASE_URL, api_endpoint)
        headers = headers or {}
        headers.update(
            {
                "Authorization": f"Token {api_key}",
            }
        )
        logger.debug(f"{log_prefix} {method} {url}. Params: {params}")
        if method == HTTPMethod.GET:
            response = requests.get(url, headers=headers, json=params)
        elif method == HTTPMethod.POST:
            response = requests.post(url, headers=headers, json=params)
        elif method == HTTPMethod.PATCH:
            response = requests.patch(url, headers=headers, json=params)
        elif method == HTTPMethod.DELETE:
            response = requests.delete(url, headers=headers, json=params)
        else:
            raise ProlificException("Invalid HTTP method.")
        response.raise_for_status()
        if response.status_code == status.HTTP_204_NO_CONTENT and not response.content:
            result = None
        else:
            result = response.json()
        logger.debug(f"{log_prefix} Response: {result}")
        return result
    except ProlificException:
        # Reraise these errors further to avoid catching them in the latest `except` condition
        raise
    except requests.exceptions.HTTPError as err:
        logger.error(f"{log_prefix} Request error: {err}. Response text: `{err.response.text}`")
        if err.response.status_code == status.HTTP_401_UNAUTHORIZED:
            raise ProlificAuthenticationError
        message = err.args[0]
        message = f"{message}. {err.response.text}"
        raise ProlificRequestError(message, status_code=err.response.status_code)
    except Exception:
        logger.exception(f"{log_prefix} Unexpected error")
        raise ProlificException