At HireArt, we try to make our customer experience better by building out integration with our customer's Applicant Tracking Systems (ATS). Building out multiple API integrations can be time-consuming and complex as more APIs are added. We decided to take a structured approach to building out our many third-party ATS integrations. Here's how we did it:
Structured Approach
We have two use cases for ATSs but we will be focusing on only one of them in this post. We source and evaluate candiates for our clients who are employers. The candidates we present to the employers should be easily accessible from the employer's ATS system. Given the wide range of ATS systems out there and their different implementations, we knew we needed a way to abstract away some of the implementation details.
We were heavily influenced by the incredible work Shopify did on ActiveMerchant. We wanted to do something similar and leverage an abstration layer that unifies third-party api calls with a transactional model.
The system we designed has 4 distinct classes:
- Transaction (Model) The parent class of all ats transactions. Common methods for creating a transaction live here.
- [ATS]Transction (Model) A transaction class that inherits from the Transaction model that's tailored to specific ATS.
- ATS This class makes all the API calls for a specific ATS implementation.
- [ATS]Authorization (Model) The implementation of an authorization for a particular ATS.
- JobReqMapping This model stores mapping configurations between the job requisitions on our system and the employer's ATS.
Transaction Model
We begin by creating a transaction model that collects the candidates we wish to send off to the ATS. Once we know which ats we're using, we simply type-cast it into a specific ATS transaction and get access to all of its methods.
Here's a high level overview of the Transaction Class
module Ats
class Transaction < ActiveRecord::Base
def has_auth?
end
def make_auth
end
def get_auth
end
def has_job_map?
end
def make_job_map
end
def get_job_map
end
def push_candidates_to_ats
end
end
end
[ATS]
Each ATS has a class with methods that access specific endpoints on that particular ATS. The ATS class is as dumb as possible. It only does 2 things, create a session with the ATS and expose methods to push candidates into the ats. Here we're using HTTParty for our web requests.
We found that for each method that makes a web request we're duplicating code for setting up the request, checking to see if the request was successful and storing any error response on the ATS isntance. So we built a general endpoint method for our Taleo Ats class. Using this method we can make any number of API calls just by invoking end_point with the proper resource route.
def end_point(url, data=nil)
full_url = "#{base_url}#{url}"
if auth_token
if data
r = HTTParty.post full_url, cookies: { 'authToken' => auth_token }, body: data, headers: { 'Content-Type' => 'application/json' }
else
r = HTTParty.get full_url, cookies: { 'authToken' => auth_token }
end
else
r = HTTParty.post full_url
end
success?(r)
r
end
def success?(response)
self.success = ( JSON.parse(response.body)["status"]["success"] == true ) rescue false
self.error_code = JSON.parse(response.body)["status"]["detail"]['errormessage']
end
A typical API call looks like this:
def candidate_insert(candidate)
end_point "/object/candidate", candidate
end
After calling candidate_insert, we can then ask ats.success? and ats.error to know if the request went through properly.
[ATS]Authorization
Authorizations for each ATS varies. The ATS Transaction model tracks which authorization to look up and then authorizes the user for an ATS instance. Authorizations do persist so future sessions can look up an previous authorizations for that user.
The most interesting part of the authorization model is the get_ats method, it creates an instance of that ats, performs the login (if the session has timed out) and returns an ats instance.
module Ats
class Authorization < ActiveRecord::Base
def get_ats
# creates an ats instance for this authorization and logs into a session on that ats
end
end
end
Sequence of Calls
To put it all together, we first create an ats_trasaction. Once we know which ats we're working with, we type-cast the transaction to a subclass of ats_transaction (ex. TaleoTransaction). This initializes a Taleo instance and if it has the credentials, creates an active session on that Taleo. Then any number of methods can then be invoked on the TaleoTransaction to interact with the ATS.