Constructing API Requests

Sequence of Events

You first need to Generate credentials for API access. Treat the secret key as you would treat a password. You will also use sets of endpoint URLs:

  • One to get an access token for a particular end user, at https://www.sidefx.com/oauth2/application_token

  • One to make API calls, at https://www.sidefx.com/api/

  1. Retrieve an access token:

    • Contatenate the three strings: client id, ":", and client_secret_key.

    • Base64-encode the contactenated string (name it <auth>).

    • Pass in an Authorization HTTP header with the value set to "Basic <auth>" where <auth> is the base64-encoded string

    • Make a POST request using https and not http to the access token endpoint URL.

    • Parse the returned body as JSON and fetch the access_token field in the result (name it <token>).

    • If the HTTP status is something other than 200, the body will contain an error message.

  2. Make API requests using the access token. You may reuse the access token over and over to make multiple API requests, but note that access tokens will eventually expire (typically within 12 hours).

    • Pass in an Authorization HTTP header with the value set to "Bearer <token>" where <token> is the access token returned previously.

    • Construct a JSON array containing three elements:

      • A string containing the name of the API function you want to call, in the form module_name.function_name.

      • A list of values of positional arguments to pass to the function.

      • A JSON object (dictionary) of keyword arguments to pass to the function.

    • Pass a json parameter in the post data with the value set to the array described above.

    • Make a POST request using https and not http to the API endpoint URL.

    • Parse the returned body as JSON.

    • If the HTTP status is something other than 200, the body will contain an error message.

Python Reference Implementation

We provide a Python implementation of what is described in the previous section to easily authenticate to our API with a convenient wrapper to make calls. Feel free to place this code in a file and import it within your script.

# Save this code to sidefx.py

import time
import json
import base64
import requests
try:
    import html.parser as HTMLParser
except ImportError:
    import HTMLParser


def service(
        access_token_url, client_id, client_secret_key, endpoint_url,
        access_token=None, access_token_expiry_time=None):
    if (access_token is None or
            access_token_expiry_time is None or
            access_token_expiry_time < time.time()):
        access_token, access_token_expiry_time = (
            get_access_token_and_expiry_time(
                access_token_url, client_id, client_secret_key))

    return _Service(
        endpoint_url, access_token, access_token_expiry_time)


class _Service(object):
    def __init__(
            self, endpoint_url, access_token, access_token_expiry_time):
        self.endpoint_url = endpoint_url
        self.access_token = access_token
        self.access_token_expiry_time = access_token_expiry_time

    def __getattr__(self, attr_name):
        return _APIFunction(attr_name, self)


class _APIFunction(object):
    def __init__(self, function_name, service):
        self.function_name = function_name
        self.service = service

    def __getattr__(self, attr_name):
        # This isn't actually an API function, but a family of them.  Append
        # the requested function name to our name.
        return _APIFunction(
            "{0}.{1}".format(self.function_name, attr_name), self.service)

    def __call__(self, *args, **kwargs):
        return call_api_with_access_token(
            self.service.endpoint_url, self.service.access_token,
            self.function_name, args, kwargs)

#---------------------------------------------------------------------------
# Code that implements authentication and raw calls into the API:


def get_access_token_and_expiry_time(
        access_token_url, client_id, client_secret_key):
    """Given an API client (id and secret key) that is allowed to make API
    calls, return an access token that can be used to make calls.
    """
    response = requests.post(
        access_token_url,
        headers={
            "Authorization": u"Basic {0}".format(
                base64.b64encode(
                    "{0}:{1}".format(
                        client_id, client_secret_key
                    ).encode()
                ).decode('utf-8')
            ),
        })
    if response.status_code != 200:
        raise AuthorizationError(
            response.status_code,
            "{0}: {1}".format(
                response.status_code,
                _extract_traceback_from_response(response)))

    response_json = response.json()
    access_token_expiry_time = time.time() - 2 + response_json["expires_in"]
    return response_json["access_token"], access_token_expiry_time


class AuthorizationError(Exception):
    """Raised from the client if the server generated an error while generating
    an access token.
    """
    def __init__(self, http_code, message):
        super(AuthorizationError, self).__init__(message)
        self.http_code = http_code


def call_api_with_access_token(
        endpoint_url, access_token, function_name, args, kwargs):
    """Call into the API using an access token that was returned by
    get_access_token.
    """
    response = requests.post(
        endpoint_url,
        headers={
            "Authorization": "Bearer " + access_token,
        },
        data=dict(
            json=json.dumps([function_name, args, kwargs]),
        ))
    if response.status_code == 200:
        return response.json()

    raise APIError(
        response.status_code,
        "{0}".format(_extract_traceback_from_response(response)))


class APIError(Exception):
    """Raised from the client if the server generated an error while calling
    into the API.
    """
    def __init__(self, http_code, message):
        super(APIError, self).__init__(message)
        self.http_code = http_code


def _extract_traceback_from_response(response):
    """Helper function to extract a traceback from the web server response
    if an API call generated a server-side exception
    """
    error_message = response.text
    if response.status_code != 500:
        return error_message

    traceback = ""
    for line in error_message.split("\n"):
        if len(traceback) != 0 and line == "</textarea>":
            break
        if line == "Traceback:" or len(traceback) != 0:
            traceback += line + "\n"

    if len(traceback) == 0:
        traceback = error_message

    return HTMLParser.HTMLParser().unescape(traceback)

Using the Python library

Assuming the previous code was saved in a sidefx.py file (or download it), here is an example on how to call the API. Replace the client_id and client_secret values with the ones associated to the Oauth application you previously created (see Generate credentials for API access).

import sidefx

if __name__ == '__main__':

    # This service object retrieve a token using your Application ID and secret
    service = sidefx.service(
        access_token_url="https://www.sidefx.com/oauth2/application_token",
        client_id='your OAuth application ID',
        client_secret_key='your OAuth application secret',
        endpoint_url="https://www.sidefx.com/api/",
    )