Help with Web API Calls

I’m trying to utilize an API on a provider’s website to first get an Access Token and then using that token, execute another call to get a list of open items in their system.
Here is a link to the doc for the Access Token:
Authentication | Get an access token
Here is the link to the Get Open Items doc:
General | List a lab’s open cases

I’m testing this in the Script Console. Below is the code I’ve tested, but keep getting a 500 error:

client		= system.net.httpClient()
url			= 'https://staging.easyrxortho.com/sso/m.php/oauth2/access_token.php'
headers		= {"grant_type": "client_credentials",
			    "client_id": "My_Client_Id",
			    "client_secret": "My_Client_Secret"}
response		= client.post(url, headers=headers)
print response

The response returned from the above is:

<Response@1444923045 'https://staging.easyrxortho.com/sso/m.php/oauth2/access_token.php' [500]>

I’ve also tried converting the headers dictionary to a string and setting it as the params parameter in place of using the headers parameter.
That looked like this:

params		= 'grant_type=client_credentials&'
params		+= 'client_id=_f7f055461271e6682c4656cf6f3511dfe55de81c68&'
params		+= 'client_secret=_9e1819a69216f2ce23c01319fa94daee4315b5ea85&'

and the post was essentially:

response		= client.post(url, params=params)

That resulted in the the echoing back of the URL with the params values and still getting a 500 error.

I don’t think I’m following the documentation for the httpClient and calls correctly.
Can someone help me out with some more generic examples to send the values for grant_type, client_id, and client_secret in a API call for this website?

Thanks!

Well, for starters, their documentation isn’t very clear.
Their authentication endpoint says to POST some data, but doesn’t indicate what format to send the data in, nor the appropriate content type. Assuming it’s JSON, you would want to do something like this, to send it as the body (not as request headers, which are a different mechanism):

client = system.net.httpClient()
url = 'https://staging.easyrxortho.com/sso/m.php/oauth2/access_token.php'
data = {
	"grant_type": "client_credentials",
	"client_id": "My_Client_Id",
	"client_secret": "My_Client_Secret"
}
response = client.post(url, data=data)
print response.text

As for making an actual authenticated request, you’ll want to pass it differently. You would extract it from the response body, then pass it as a constructed bearer Authorization header.

Getting the token once only delays the problem, though. OAuth tokens are deliberately short-lived. You’ll need to abstract away some logic to refresh the token automatically before you can actually run this in production.

I would expect a minimal working example to resemble something like this:

client = system.net.httpClient()
baseUrl = "https://staging.easyrxortho.com/"
url = baseUrl + '/sso/m.php/oauth2/access_token.php'
data = {
	"grant_type": "client_credentials",
	"client_id": "My_Client_Id",
	"client_secret": "My_Client_Secret"
}
response = client.post(url, data)
token = response.json["access_token"]

data = client.get(baseUrl + "/webservice/get_open_cases", headers = {"Authorization: Bearer %s" % token})

print data.json

For real use, I would probably wrap this up into a class in order to maintain state. You would construct an EasyRxClient(clientId, clientSecret), then create your own instance methods that call into the API. Those instance methods would automatically obtain/refresh an access token as appropriate.

For testing & experimentation, I would recommend looking into a third party tool to get the API response working, then checking how to adapt them to Ignition. POSTMan is a popular name, but there’s a million and one HTTP clients out there.

I think content type is
Content-Type": “application/x-www-form-urlencoded”
One of our web guys gave me that, so you are correct, not in the doc.

We’ll be running this on a timed basis, once an hour, as that is the rate that the EasyRX system updates the cases. Would a class constructor be necessary in that case? I’ve never personally built and utilized classes like this in Ignition.

Right, so the content-type out of Ignition is going to be application/json unless you jump through some hoops (for now) - see this post for some useful details:

Here's the thing. Notice the example success response to the token request has an 'expires_in' field:
image
Assuming that's seconds (undocumented) and assuming it's 3600 seconds in a real response (again, undocumented :slight_smile:) your token will last exactly an hour. The proper way to utilize an OAuth API is to authorize once, then continuously refresh your token indicating you're continuing to use it. What will happen if you just re-authenticate "from scratch" each time you use the API is up to the server, really.

If this is a one-off (there's only one place you will ever be retrieving this data, and you don't expect it to apply to more sites) then there's not a huge advantage to the class based syntax. For clarity, I was picturing something like this:

class EasyRxClient(object):
	def __init__(self, clientId, clientSecret, baseUrl = "https://staging.easyrxortho.com/"):
		self.baseUrl = baseUrl
		self.clientId = clientId
		self.clientSecret = clientSecret
		self.client = system.net.httpClient()
		self.__refreshToken = None
		self.__accessToken = None

	def getOrUpdateAccessToken(self):
		# have some conditional logic, e.g. based on token expiry, to request a new access token/refresh the existing token here
		return __accessToken;

	def getOpenCases(self):
		token = self.getOrUpdateAccessToken()
		return self.client.get(self.baseUrl + "/webservice/get_open_cases")

Which has the advantage of being much cleaner at use site - wherever you care about this data, it's trivial to retrieve:

easyRx = EasyRxClient(clientId, clientSecret)
openCases = easyRx.getOpenCases()

But I don't know your environment, your business needs, how comfortable you & your organization are with Python, how much this might need to change over time, etc. That's ultimately a judgement call for you to make, I just mentioned it as a syntactic possibility.

2 Likes

Everything that was mentioned as “undocumented” may be assumed from the OAuth 2.0 spec. You might want to read over RFC 6749 - The OAuth 2.0 Authorization Framework

2 Likes

I was able to get this to work.
I had to import the urlencode function of the urllib library to encode the data being passed in. Which included the “grant_type”, explaining why the response I was getting kept saying “unsupported_grant_type”.
The get open cases method of the API was a little more straight forward (no explicit encoded required). Hence I was able to get this working.
Thanks Paul!

2 Likes