API Samples Overview

The Issuetrak API is a RESTful API that uses the HTTP protocol as the primary transport infrastructure both to invoke and API operation and to transmit the response from an API operation back to the API consumer. As such, the API is platform-agnostic.

Any API consumer platform that can transmit the appropriately defined payload via a valid HTTP application protocol request using the API HMAC authorization scheme (see the API Authorization Overview article for details about the authorization scheme) can communicate with the API.

Issuetrak has created two sample applications to demonstrate how to communicate with the API using Microsoft .NET and PHP.  An additional sample application is included for reading and updating user defined fields.  The samples can be accessed here


 

.NET Sample

The .NET API sample relies upon an Issuetrak.API.Client library distributed with the sample as a .NET assembly. This Issuetrak.API.Client includes a set of wrapper methods to encapsulate the details of the process of calling the Issuetrak API for a particular operation.

For example, to retrieve a Note for a specific Note ID value, the Issuetrak.API.Client library exposes the following method signature:

Task<IssuetrakAPIResponse<ReadNoteDTO>> GetNoteForNoteIDAsync(GetNoteForNoteIDRequest request)

This signature indicates that the wrapper method is an asynchronous method that will return a Task with a task result of type <IssuetrakAPIResponse<ReadNoteDTO>>. The IssuetrakAPIResponse class provides a wrapper to encapsulate the details of an API response with properties such as “ResponseStatusCode” to retrieve the HTTP status code of the HTTP response and “ResponseText” to provide the raw string response of the HTTP response.

To call the GetNoteForNoteIDAsync API method , the Issuetrak.API.Client library may be invoked as in the following C# example:

using (IssuetrakAPIClient client = new IssuetrakAPIClient(API_KEY)) 
{ 
  GetNoteForNoteIDRequest request = 
     new GetNoteForNoteIDRequest(BASE_API_URL, API_VERSION, noteID);
  IssuetrakAPIResponse response = await client.GetNoteForNoteIDAsync(request);
}

In the sample, the “API_KEY” constant is the API key text value that is used for the API authorization scheme. The “BASE_API_URL” constant is the root URL to the Issuetrak API instance, and the API_VERSION constant is the integer identifier for the desired API version, e.g., 1.

The “noteID” parameter is a variable that is used to identify the desired Note ID value to retrieve.


 

PHP Sample

The PHP sample project demonstrates how to interact with the Issuetrak API via standard PHP functions to include code samples to:

  1. Create a New Note
  2. Retrieve a Note by Note ID
  3. Retrieve Issues by Issue Number List

The PHP sample also demonstrates how to employ standard PHP functions to generate the authorization headers for API authorization.

The PHP sample is included in the “index.php” file reproduced in full below. To execute the PHP sample, copy the text for the “index.php” file to an appropriately configured PHP web server and update the “$base_url” and “$api_key” PHP variables with the appropriate values for the Issuetrak API deployment.

PHP Sample:
<?php
$base_url = "http://mysite.example.com";
$api_key = "API KEY GOES HERE";
$hash_algorithm = "sha512";


testGetRequest();
testPostRequest();


function testGetRequest()
{
	$request_method = "GET";
	$request_path = "/api/v1/notes/1";
	$request_query = "";
	$request_body = "";

	$response = ExecuteApiCall($request_method, $request_path, $request_query, $request_body);

	echo "RESPONSE = " . $response . "\n";
}

function testPostRequest()
{
	$request_method = "POST";
	$request_path = "/api/v1/notes";
	$request_query = "";
	$request_body = <<<END
{
	"IssueNumber": 1,
	"CreatedDate": "2018-01-01T12:32:32-04:00",
	"CreatedBy": "APIUser",
	"ShouldSuppressEmailForCreateOperation": false,
	"NoteText": "Testing note creation from PHP.",
	"IsPrivate": false,
	"IsRichText": false
}
END;

	$response = ExecuteApiCall($request_method, $request_path, $request_query, $request_body);

	echo "RESPONSE = " . $response . "\n";
}

function ExecuteApiCall($method, $requestPath, $requestQuery, $requestBody)
{
	date_default_timezone_set("UTC");
	$request_time_iso8601 = gmdate("Y-m-d\TH:i:s") . substr((string)microtime(), 1, 8) . "Z";
	$request_guid = guid();

	$request_summary = implode("\n", [$method, strtolower($request_guid), $request_time_iso8601, strtolower($requestPath), 	$requestQuery, $requestBody]);
	$request_signature = base64_encode(hash_hmac($GLOBALS['hash_algorithm'], $request_summary, $GLOBALS['api_key'], true));

	$http_headers = array(
		"Content-Type: application/json",
		"X-Issuetrak-API-Timestamp: " . $request_time_iso8601,
		"X-Issuetrak-API-Request-ID: " . $request_guid,
		"X-Issuetrak-API-Authorization: " . $request_signature
	);


	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $GLOBALS['base_url'] . $requestPath . $requestQuery);
	curl_setopt($curl, CURLOPT_HTTPHEADER, $http_headers);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

	if($method === 'POST')
	{
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, $requestBody);
	}

	$response = curl_exec($curl);
	curl_close($curl);

	return $response;
}

function guid()
{
	if (function_exists('com_create_guid'))
	{ # this happens on Windows
		return com_create_guid();
	}
	else
	{ # this happens on other platforms

		$charid = md5(uniqid(rand(), true));
		$hyphen = chr(45); # "-"
		$uuid = substr($charid, 0, 8) . $hyphen
			. substr($charid, 8, 4) . $hyphen
			. substr($charid,12, 4) . $hyphen
			. substr($charid,16, 4) . $hyphen
			. substr($charid,20,12);

	return $uuid;
	}
}

?>

Python Samples

The Python sample projects demonstrate how to interact with the Issuetrak API via Python to:

  1. Retrieve an Issue
  2. Update an Issue
  3. Submit an Issue

Python 2.6:

from uuid import uuid4
from hashlib import sha512
from datetime import datetime
from urlparse import urlparse
import hmac
import json
import base64
import httplib

SITE_URL = "http://subdomain.myissuetraksite.com/nestedsite1/nestedsite2/api/v1"
API_KEY = "dUd4ZzQ3dDNvM1lrYzFoY1lHYmh3anRjQkJHb1dBWWdWTHdOd25kZm9GQQ="

class ApiAuthorization:
    __apiKey = ""

    def __init__(self, apiKey):
        self.__apiKey = apiKey

    def __generateMessageToHash(self, httpVerb, requestId, requestTimeStamp, absoluteUrlPath, requestQuery = "", requestBody = ""):
        return "\n".join([httpVerb, requestId, requestTimeStamp, absoluteUrlPath, requestQuery, requestBody])

    def __computeHash(self, apiKey, messageToBeHashed):
        hashText = None
        hashKeyBytes = str(apiKey).encode('UTF-8')
        messageToBeHashedBytes = str(messageToBeHashed).encode('UTF-8')

        hmacHash = hmac.new(hashKeyBytes, messageToBeHashedBytes, sha512).digest()
        hashText = base64.b64encode(hmacHash).decode('UTF-8')
        
        return hashText

    @staticmethod
    def __getUtcDate():
        return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f0Z")

    def __getConnection(self, protocol, url):
        return httplib.HTTPConnection(url, 80) if (protocol == "http") else httplib.HTTPSConnection(url, 443)

    def __generateHeaders(self, requestId, requestTimestamp, hashedMessage):
        return {
            "X-IssueTrak-API-Request-ID": requestId,
            "X-IssueTrak-API-Timestamp": requestTimestamp,
            "X-IssueTrak-API-Authorization": hashedMessage,
            "content-type": "application/json"
        }

    def performApiCall(self, httpVerb, endpointUrl, requestQuery = "", requestBody = ""):
        urlParts = urlparse(endpointUrl)
        baseUrl = urlParts.netloc.lower()
        absolutePath = urlparse(endpointUrl).path.lower()
        requestId = str(uuid4()).lower()
        requestTimestamp = self.__getUtcDate()
        messageToHash = self.__generateMessageToHash(httpVerb, requestId, requestTimestamp, absolutePath, requestQuery, requestBody)
        hashedMessage = self.__computeHash(self.__apiKey, messageToHash)
        
        connection = self.__getConnection(urlParts.scheme, baseUrl)
        headers = self.__generateHeaders(requestId, requestTimestamp, hashedMessage)

        connection.request(httpVerb, endpointUrl, requestBody, headers)
        return connection.getresponse()

def performGet(siteUrl, apiKey, endpointUrl):
    url = "{0}{1}".format(siteUrl, endpointUrl)
    apiAuthorization = ApiAuthorization(apiKey)
    return apiAuthorization.performApiCall("GET", url)

def performPost(siteUrl, apiKey, endpointUrl, requestQuery = '', requestBody = ''):
    url = "{0}{1}".format(siteUrl, endpointUrl)
    apiAuthorization = ApiAuthorization(apiKey)
    return apiAuthorization.performApiCall("POST", url, requestQuery, requestBody)

def performPut(siteUrl, apiKey, endpointUrl, requestQuery = '', requestBody = ''):
    url = "{0}{1}".format(siteUrl, endpointUrl)
    apiAuthorization = ApiAuthorization(apiKey)
    return apiAuthorization.performApiCall("PUT", url, requestQuery, requestBody)

#######################################
# GET
# Retrieves issue #1 from issuetrak
#######################################
print("------Issue Retrieve------")
response = performGet(SITE_URL, API_KEY, "/issues/false/1")
getResponseData = response.read().decode()
print(getResponseData)

#######################################
# PUT
# Updates issue #1 in issuetrak
#######################################
updateData = json.loads(getResponseData)

# Remove fields not on the issue update DTO
del updateData["Metadata"]
del updateData["ExtensionData"]
del updateData["Notes"]
del updateData["SubmittedDate"]
del updateData["SLAComplianceStatus"]
del updateData["Slaid"]
for userDefinedField in updateData["UserDefinedFields"]:
    del userDefinedField["ExtensionData"]
    del userDefinedField["Metadata"]
    del userDefinedField["DisplayName"]

# Remove fields that are disabled or inactive
del updateData["CauseID"]
del updateData["DepartmentID"]
del updateData["IssueSubType4ID"]

# Make changes to existing fields with updated data
updateData["Subject"] = "Altering my subject"

# Perform request
print("\n\n------Issue Update------")
response = performPut(SITE_URL, API_KEY, "/issues", "", json.dumps(updateData))
print(response.read().decode())

#######################################
# POST
# Insert issue into Issuetrak
#######################################

# Set disabled/inactive fields to None
requestBody = {
  "ShouldSuppressEmailForCreateOperation": True,
  "Notes": [
    {
      "CreatedDate": "{0}".format(datetime.now()),
      "CreatedBy": "admin",
      "NoteText": "This is a note",
      "IsPrivate": False,
      "IsRichText": True
    }
  ],
  "UserDefinedFields": [
    {
      "UserDefinedFieldID": 1,
      "Value": "This is text that will go into one of my user defined fields"
    },
    {
      "UserDefinedFieldID": 1008,
      "Value": "{0}".format(datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    }
  ],
  "SubmittedDate": "{0}".format(datetime.now()),
  "EnteredBy": "admin",
  "SeverityID": None,
  "Subject": "This is a test subject submitted via the Issuetrak API in Python",
  "Description": "This is the description of the test issue",
  "IsDescriptionRichText": True,
  "IssueTypeID": 1,
  "IssueSubTypeID": 0,
  "IssueSubType2ID": 0,
  "IssueSubType3ID": 0,
  "IssueSubType4ID": None,
  "PriorityID": 1,
  "AssetNumber": 0,
  "LocationID": "",
  "SubmittedBy": "admin",
  "AssignedTo": None,
  "TargetDate": None,
  "RequiredByDate": None,
  "NextActionTo": None,
  "SubStatusID": 0,
  "ProjectID": 0,
  "OrganizationID": 1,
  "ShouldNeverSendEmailForIssue": False,
  "ClassID": 1,
  "DepartmentID": None,
  "SpecialFunction1": "string",
  "SpecialFunction2": "string",
  "SpecialFunction3": "string",
  "SpecialFunction4": "string",
  "SpecialFunction5": "string"
}

print("\n\n------Insert Issue------")
response = performPost(SITE_URL, API_KEY, "/issues", "", json.dumps(requestBody))
print(response.read().decode())

Python 3.7:

from uuid import uuid4
from urllib.parse import urlparse
from hashlib import sha512
from datetime import datetime
import hmac
import json
import base64
import http.client

SITE_URL = "http://subdomain.myissuetraksite.com/nestedsite1/nestedsite2/api/v1"
API_KEY = "dUd4ZzQ3dDNvM1lrYzFoY1lHYmh3anRjQkJHb1dBWWdWTHdOd25kZm9GQQ="

class ApiAuthorization:
    __apiKey = ""
    __siteUrl = ""

    def __init__(self, apiKey, siteUrl):
        self.__apiKey = apiKey
        self.__siteUrl = siteUrl

    def __generateMessageToHash(self, httpVerb, requestId, requestTimeStamp, absoluteUrlPath, requestQuery = "", requestBody = ""):
        return "\n".join([httpVerb, requestId, requestTimeStamp, absoluteUrlPath, requestQuery, requestBody])

    def __computeHash(self, apiKey, messageToBeHashed):
        hashText = None
        hashKeyBytes = bytes(apiKey, 'UTF-8')
        messageToBeHashedBytes = bytes(messageToBeHashed, 'UTF-8')

        hmacHash = hmac.new(hashKeyBytes, messageToBeHashedBytes, sha512).digest()
        hashText = base64.b64encode(hmacHash).decode('UTF-8')
        
        return hashText

    @staticmethod
    def __getUtcDate():
        return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f0Z")

    def __getConnection(self, protocol, url):
        return http.client.HTTPConnection(url, 80) if (protocol == "http") else http.client.HTTPSConnection(url, 443)

    def __generateHeaders(self, requestId, requestTimestamp, hashedMessage):
        return {
            "X-IssueTrak-API-Request-ID": requestId,
            "X-IssueTrak-API-Timestamp": requestTimestamp,
            "X-IssueTrak-API-Authorization": hashedMessage,
            "content-type": "application/json"
        }

    def __performApiCall(self, httpVerb, endpointUrl, requestQuery = "", requestBody = ""):
        fullUrl = f"{self.__siteUrl}{endpointUrl}"
        urlParts = urlparse(fullUrl)
        baseUrl = urlParts.netloc.lower()
        absolutePath = urlparse(fullUrl).path.lower()
        requestId = str(uuid4()).lower()
        requestTimestamp = self.__getUtcDate()
        messageToHash = self.__generateMessageToHash(httpVerb, requestId, requestTimestamp, absolutePath, requestQuery, requestBody)
        hashedMessage = self.__computeHash(self.__apiKey, messageToHash)
        
        connection = self.__getConnection(urlParts.scheme, baseUrl)
        headers = self.__generateHeaders(requestId, requestTimestamp, hashedMessage)

        connection.request(httpVerb, fullUrl, requestBody, headers)
        return connection.getresponse()

    def performGet(self, endpointUrl):
        return self.__performApiCall("GET", endpointUrl)

    def performPost(self, endpointUrl, requestQuery = '', requestBody = ''):
        return self.__performApiCall("POST", endpointUrl, requestQuery, requestBody)

    def performPut(self, endpointUrl, requestQuery = '', requestBody = ''):
        return self.__performApiCall("PUT", endpointUrl, requestQuery, requestBody)

#######################################
# Initialize Api Authorization Class
#######################################
apiAuthorization = ApiAuthorization(API_KEY, SITE_URL)

#######################################
# GET
# Retrieves issue #1 from issuetrak
#######################################
print("------Issue Retrieve------")
response = apiAuthorization.performGet("/issues/false/1")
getResponseData = response.read().decode()
print(getResponseData)

#######################################
# PUT
# Updates issue #1 in issuetrak
#######################################
updateData = json.loads(getResponseData)

# Remove fields not on the issue update DTO
del updateData["Metadata"]
del updateData["ExtensionData"]
del updateData["Notes"]
del updateData["SubmittedDate"]
del updateData["SLAComplianceStatus"]
del updateData["Slaid"]
for userDefinedField in updateData["UserDefinedFields"]:
    del userDefinedField["ExtensionData"]
    del userDefinedField["Metadata"]
    del userDefinedField["DisplayName"]

# Remove fields that are disabled or inactive
del updateData["CauseID"]
del updateData["DepartmentID"]
del updateData["IssueSubType4ID"]

# Make changes to existing fields with updated data
updateData["Subject"] = "Altering my subject"

# Perform request
print("\n\n------Issue Update------")
response = apiAuthorization.performPut("/issues", "", json.dumps(updateData))
print(response.read().decode())

#######################################
# POST
# Insert issue into Issuetrak
#######################################

# Set disabled/inactive fields to None
requestBody = {
  "ShouldSuppressEmailForCreateOperation": True,
  "Notes": [
    {
      "CreatedDate": f"{datetime.now()}",
      "CreatedBy": "admin",
      "NoteText": "This is a note",
      "IsPrivate": False,
      "IsRichText": True
    }
  ],
  "UserDefinedFields": [
    {
      "UserDefinedFieldID": 1,
      "Value": "This is text that will go into one of my user defined fields"
    },
    {
      "UserDefinedFieldID": 1008,
      "Value": f"{datetime.now().strftime('%Y-%m-%dT%H:%M:%S')}"
    }
  ],
  "SubmittedDate": f"{datetime.now()}",
  "EnteredBy": "admin",
  "SeverityID": None,
  "Subject": "This is a test subject submitted via the Issuetrak API in Python",
  "Description": "This is the description of the test issue",
  "IsDescriptionRichText": True,
  "IssueTypeID": 1,
  "IssueSubTypeID": 0,
  "IssueSubType2ID": 0,
  "IssueSubType3ID": 0,
  "IssueSubType4ID": None,
  "PriorityID": 1,
  "AssetNumber": 0,
  "LocationID": "",
  "SubmittedBy": "admin",
  "AssignedTo": None,
  "TargetDate": None,
  "RequiredByDate": None,
  "NextActionTo": None,
  "SubStatusID": 0,
  "ProjectID": 0,
  "OrganizationID": 1,
  "ShouldNeverSendEmailForIssue": False,
  "ClassID": 1,
  "DepartmentID": None,
  "SpecialFunction1": "string",
  "SpecialFunction2": "string",
  "SpecialFunction3": "string",
  "SpecialFunction4": "string",
  "SpecialFunction5": "string"
}

print("\n\n------Insert Issue------")
response = apiAuthorization.performPost("/issues", "", json.dumps(requestBody))
print(response.read().decode())