def METHOD_NAME(self, method, path, **kwargs):
    """
    Make an API http request.
    :param method: HTTP method
    :param path: path of the endpoint.
        Relative paths are expanded by `BasicClient.api_url`. Absolute URLs are used verbatim.
    :param kwargs: extra arguments for the requests
    :raises ExternalRequestError: For any request based failure
    """
    if path.startswith("/"):
        path = self.api_url(path)
    try:
        return self._oauth_http_service.METHOD_NAME(method, path, **kwargs)
    except ExternalRequestError as err:
        status_code = getattr(err.response, "status_code", None)
        response_text = getattr(err.response, "text", "")
        err.refreshable = status_code == 401
        if status_code == 403 and "Insufficient scope to call" in response_text:
            # This handles the case were the token was originally issued
            # for a set of scopes, a feature that needs extra scopes is
            # enabled, and a request for that feature fails.
            # Raising OAuth2TokenError re-starts the oauth2 flow, getting
            # a new token with the correct scopes. This won't handle the
            # case were the schools install doesn't have the required
            # scopes. In that case the request doesn't make its way our
            # server.
            raise OAuth2TokenError(refreshable=False) from err
        if (
            "http://docs.valence.desire2learn.com/res/apiprop.html#invalid-token"
            in response_text
        ):
            # There's a few known responses from D2L we rather tackle explicitly as OAuth2TokenError.
            # They are part of regular operation and they would otherwise fill up the logs with noise.
            raise OAuth2TokenError(refreshable=err.refreshable) from err
        raise